Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently completely unused, and an expensive acquire(100) request comes. It would be nonsensical to just wait for 100 seconds, and /then/ start the actual task. Why wait without doing anything? A much better approach is to /allow/ the request right away (as if it was an acquire(1) request instead), and postpone /subsequent/ requests as needed. In this version, we allow starting the task immediately, and postpone by 100 seconds future requests, thus we allow for work to get done in the meantime instead of waiting idly.
前一篇文章提到了限流的几种常见算法,本文将分析guava限流类
RateLimiter
的实现。RateLimiter
有两个实现类:SmoothBursty
和SmoothWarmingUp
,其都是令牌桶算法的变种实现,区别在于SmoothBursty
加令牌的速度是恒定的,而SmoothWarmingUp
会有个预热期,在预热期内加令牌的速度是慢慢增加的,直到达到固定速度为止。其适用场景是,对于有的系统而言刚启动时能承受的QPS较小,需要预热一段时间后才能达到最佳状态。更多文章见个人博客:https://github.com/farmerjohngit/myblog
基本使用
RateLimiter
的使用很简单:输出如下:
需要注意的是,当令牌不足时,
acquire
方法并不会阻塞本次调用,而是会算在下次调用的头上。比如第一次调用时,令牌桶中并没有令牌,但是第一次调用也没有阻塞,而是在第二次调用的时候阻塞了1秒。也就是说,每次调用欠的令牌(如果桶中令牌不足)都是让下一次调用买单。输出如下:
这样设计的目的是:
简单的说就是,如果每次请求都为本次买单会有不必要的等待。比如说令牌增加的速度为每秒1个,初始时桶中没有令牌,这时来了个请求需要100个令牌,那需要等待100s后才能开始这个任务。所以更好的办法是先放行这个请求,然后延迟之后的请求。
另外,RateLimiter还有个
tryAcquire
方法,如果令牌够会立即返回true,否则立即返回false。源码分析
本文主要分析
SmoothBursty
的实现。首先看
SmoothBursty
中的几个关键字段:RateLimiter的创建
先看创建RateLimiter的create方法。
create方法主要就是创建了一个
SmoothBursty
实例,并调用了其setRate
方法。注意这里的maxBurstSeconds
写死为1.0。setRate
方法中设置了maxPermits=maxBurstSeconds * permitsPerSecond
;而maxBurstSeconds
为1,所以maxBurstSeconds
只会保存1秒中的令牌数。需要注意的是
SmoothBursty
是非public的类,也就是说只能通过RateLimiter.create
方法创建,而该方法中的maxBurstSeconds
是写死1.0的,也就是说我们只能创建桶大小为permitsPerSecond*1的SmoothBursty
对象(当然反射的方式不在讨论范围),在guava的github仓库里有好几条issue(issue1,issue2,issue3,issue4)希望能由外部设置maxBurstSeconds
,但是并没有看到官方人员的回复。而在唯品会的开源项目[vjtools]()中,有人提出了这个问题,唯品会的同学对guava的RateLimiter进行了拓展。对于guava的这样设计我很不理解,有清楚的朋友可以说下~
到此为止一个
SmoothBursty
对象就创建好了,接下来我们分析其acquire
方法。acquire方法
acquire
中会调用reserve
方法获得当前请求需要等待的时间,然后进行休眠。reserve
方法最终会调用到reserveEarliestAvailable
,在该方法中会先调用上文提到的resync
方法对桶中的令牌进行补充(如果需要的话),然后减少桶中的令牌,以及计算这次请求欠的令牌数及需要等待的时间(由下次请求负责等待)。如果上一次请求没有欠令牌或欠的令牌已经还清则返回值为
nowMicros
,否则返回值为上一次请求缺少的令牌个数*生成一个令牌所需要的时间。End
本文讲解了
RateLimiter
子类SmoothBursty
的源码,对于另一个子类SmoothWarmingUp
的原理大家可以自行分析。相对于传统意义上的令牌桶,RateLimiter
的实现还是略有不同,主要体现在一次请求的花费由下一次请求来承担这一点上。