Java 并发编程实战
王宝令
资深架构师
71384 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
学习攻略 (1讲)
Java 并发编程实战
15
15
1.0x
00:00/00:00
登录|注册

38 | 案例分析(一):高性能限流器Guava RateLimiter

从今天开始,我们就进入案例分析模块了。 这个模块我们将分析四个经典的开源框架,看看它们是如何处理并发问题的,通过这四个案例的学习,相信你会对如何解决并发问题有个更深入的认识。
首先我们来看看 Guava RateLimiter 是如何解决高并发场景下的限流问题的。Guava 是 Google 开源的 Java 类库,提供了一个工具类 RateLimiter。我们先来看看 RateLimiter 的使用,让你对限流有个感官的印象。假设我们有一个线程池,它每秒只能处理两个任务,如果提交的任务过快,可能导致系统不稳定,这个时候就需要用到限流。
在下面的示例代码中,我们创建了一个流速为 2 个请求 / 秒的限流器,这里的流速该怎么理解呢?直观地看,2 个请求 / 秒指的是每秒最多允许 2 个请求通过限流器,其实在 Guava 中,流速还有更深一层的意思:是一种匀速的概念,2 个请求 / 秒等价于 1 个请求 /500 毫秒。
在向线程池提交任务之前,调用 acquire() 方法就能起到限流的作用。通过示例代码的执行结果,任务提交到线程池的时间间隔基本上稳定在 500 毫秒。
//限流器流速:2个请求/秒
RateLimiter limiter =
RateLimiter.create(2.0);
//执行任务的线程池
ExecutorService es = Executors
.newFixedThreadPool(1);
//记录上一次执行时间
prev = System.nanoTime();
//测试执行20次
for (int i=0; i<20; i++){
//限流器限流
limiter.acquire();
//提交任务异步执行
es.execute(()->{
long cur=System.nanoTime();
//打印时间间隔:毫秒
System.out.println(
(cur-prev)/1000_000);
prev = cur;
});
}
输出结果:
...
500
499
499
500
499

经典限流算法:令牌桶算法

Guava 的限流器使用上还是很简单的,那它是如何实现的呢?Guava 采用的是令牌桶算法,其核心是要想通过限流器,必须拿到令牌。也就是说,只要我们能够限制发放令牌的速率,那么就能控制流速了。令牌桶算法的详细描述如下:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(60)

  • 最新
  • 精选
  • null
    re:为什么令牌是从令牌桶中出的,那么 next 就无需增加一个 interval? next 变量的意思是下一个令牌的生成时间,可以理解为当前线程请求的令牌的生成时刻,如第一张图所示:线程 T1 的令牌的生成时刻是第三秒。 线程 T 请求时,存在三种场景: 1. 桶里有剩余令牌。 2. 刚创建令牌,线程同时请求。 3. 桶里无剩余令牌。 场景 2 可以想象成线程请求的同时令牌刚好生成,没来得及放入桶内就被线程 T 拿走了。因此将场景 2 和场景 3 合并成一种情况,那就是桶里没令牌。即线程请求时,桶里可分为有令牌和没令牌。 “桶里没令牌”,线程 T 需要等待;需要等待则意味着 now(线程 T 请求时刻) 小于等于 next(线程 T 所需的令牌的生成时刻)。这里可以想象一下线程 T 在苦苦等待令牌生成的场景,只要线程 T 等待那么久之后,就会被放行。放行这一刻令牌同时生成,立马被线程拿走,令牌没放入桶里。对应到代码就是 resync 方法没有进入 if 语句内。 “桶里有令牌”,线程 T 不需要等待。说明线程 T 对应的令牌已经早早生成,已在桶内。代码就是:now > next(请求时刻大于对应令牌的生成时刻)。因此在分配令牌给线程之前,需要计算线程 T 迟到了多久,迟到的这段时间,有多少个令牌生成¹;然后放入桶内,满了则丢弃²;未来的线程的令牌在这个时刻已经生成放入桶内³(即 resync 方法的逻辑)。线程无需等待,所以不需要增加一个 interval 了。 角标分别对应 resync 方法内的代码: ¹: long newPermits=(now-next)/interval; ²: storedPermits=min(maxPermits, storedPermits + newPermits); ³: next = now;

    作者回复: 👍条理清晰

    6
    68
  • 花儿少年
    很精髓的就是reserve方法,我来试着稍微解释一下 首先肯定是计算令牌桶里面的令牌数量 然后取令牌桶中的令牌数量storedPermits 与当前的需要的令牌数量 1 做比较,大于等于 1,说明令牌桶至少有一个令牌,此时下一令牌的获取是不需要等待的,表现为 next 不需要变化;而当令牌桶中的令牌没有了即storedPermits等于 0 时,next 就会变化为下一个令牌的获取时间,注意 nr 的值变化

    作者回复: 👍

    7
    25
  • 梦倚栏杆
    有个疑问:高并发情况下单独一个线程维护一个队列放令牌,性能上扛不住,那么获取令牌时每次加锁去计算性能就可以抗的主?是根据什么依据来判断性能的呢?

    作者回复: 高并发场景下,CPU忙碌,大概率会出现就绪的线程被积压,对于定时放令牌的线程,其定时器会被大概率的延迟

    4
    19
  • Darren
    老师,请教一下,限流器和信号量为什么感觉一样的,那为什么2个还都存在?是因为业务场景不同吗?请老师解惑下

    作者回复: 限流器不需要释放操作,信号量没办法控制带时间范围的限流,只能用于非常简单的场景

    2
    17
  • zsh0103
    老师好,问个问题。文中代码b=3,r=1/s时,如果在next之后同时来了3个请求,应该时都可以获得令牌的对吧。就是说这3个请求都可以执行。那岂不是违背了r=1/s的限制吗。

    作者回复: 按照令牌桶算法是这样的,所以b不能搞得太大

    8
  • 刘鸿博
    newPermits, storePermits, fb, nr 都应该是double, 而不是long.

    作者回复: 示例代码只是为了更容易理解,实际应用还是要参考guava的实现

    2
    5
  • 爱吃回锅肉的瘦子
    老师,有没什么资料推荐关于guava预热功能呢?主要网上资料太繁杂,不知道要如何甄别哪些是比较经典的

    作者回复: 能把为什么用的是那个积分函数,而不是用其他积分函数讲清楚的,应该是比较好的。最好是看guava的代码注视,写的非常详细。

    4
  • 高源
    还有就是老师我问一下因为我不是在互联网公司工作接触高并发场景少,我又喜欢学习研究提高自己,是不是得多看多练,实战

    作者回复: 最好还是找个互联网企业,有些问题只有在很高的并发压力下才会爆发。不过技术没有互联网之分,只要基础牢,成长会很快。多看基础性的东西,一定带着问题去看。

    4
  • 小强(jacky)
    老师请教个问题,maxPermits/next 的变量在程序里面,不同线程之间存在依赖关系,这不是数据竞争吗?为啥这里没有加对应的锁?

    作者回复: 主要是通过synchronized关键字搞的

    3
  • 很精彩!老师应该去讲数据结构与算法:)

    作者回复: 何必难为自己呢,不讲了😄

    2
    3
收起评论
显示
设置
留言
60
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部