Guava RateLimiter是一个谷歌提供的限流工具,可以有效限定单个JVM实例上某个接口的流量。
RateLimiter抽象类提供限流的所有功能,它的实现类只有SmoothRateLimiter。而SmoothRateLimiter的具体策略又由它的两个内部子类来实现。
SmoothBursty:兼容突发流量的令牌桶实现,也就是上一节描述的经典令牌桶算法。
SmoothWarmingUp:带预热过程的令牌桶实现,即在桶较满时产生令牌的速度较慢,随着令牌的消耗慢慢增长到恒定速率。如下图所示,x轴为令牌数,y轴为延迟,从右向左看的梯形区域就是令牌消耗的预热过程。
由上图看出,通过RateLimiter的重载create()方法,返回不同的实现类。
以SmoothBursty为例。首先看SmoothRateLimiter四个参数的含义:
从后往前看获取令牌的过程:首先,RateLimitermo提供了acquire()的默认实现,acquire()是阻塞式获得令牌的一种方法,其逻辑为:首先由reserve方法计算出需要获取指定数量领牌的时间,再sleep该时间。可以看出它的核心是reserve()方法。
可以看出,reserve方法是通过同步加锁的方式来获取令牌的。其中加锁的对象是mutex()返回的一个单例对象:
回到reserve()方法,继续向上溯源:
reserveAndGetWaitLength方法的作用是预定令牌并且返回需要等待的时间。acquire和tryAcquire的底层调用的都是该方法。
最终,可以看出,RateLimiter交给其SmoothRateLimiter类实现的是reserveEarliestAvailable方法。
SmoothRateLimiter仍然是一个抽象类,它提供了部分方法的默认实现,比如setRate和reserveEarliestAvailable。从方法名中我们就可以看出来,reserveEarliestAvailable的功能是预定令牌。
其逻辑为:
void resync(long nowMicros) {
if (nowMicros > nextFreeTicketMicros) {
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
// SmoothBursty.coolDownIntervalMicros()
@Override
double coolDownIntervalMicros() {
return stableIntervalMicros;
}
生成令牌的逻辑为:
首先判断当前时间是否超过了下一个令牌时间戳,即可以处理请求的时间戳。
如果没有超过令牌时间戳,则无需更新令牌桶状况,此时桶内情况可以直接参与预定令牌的时间计算,得到的需要等待的时间。
如果当前时间已经超过了该时间戳,则会计算出从时间戳到当前时间共产生了多少令牌,更新桶中的情况和时间戳。结合上文,此时的时间戳还需要加上未来成产所需令牌的时间,才会是下一次可处理请求的时间。
可见,RateLimiter的令牌是延迟(lazy)生成的,也就是说每次受理当前请求时,如果系统已经空闲了一定时间,才会计算上次请求到当前时间应该产生多少个令牌,而不是使用单独的任务来定期产生令牌——因为定时器无法保证较高的精度,并且性能不佳。当然,如果令牌已经“超支”,当前就不需要再更新令牌了。
extra: semaphore信号量也有着类似于令牌桶的概念,可以对比学习。