Guava RateLimiter使用和源码分析
使用
示例
RateLimiter limiter = RateLimiter.create(5); // 这里的1表示每秒允许处理的量为1个
for (int i = 1; i <= 10; i++) {
limiter.acquire();// 请求RateLimiter, 超过permits会被阻塞
}
double waitTime=rateLimiter.acquire(1);//acquire方法传入的是需要的令牌个数,当令牌不足时会进行等待,该方法返回的是等待的时间
boolean isAcquire = limiter.tryAcquire(); //立刻失败
boolean isAcquire = limiter.tryAcquire(100, TimeUnit.MILLISECONDS);//等待一段时间后返回失败
需要注意的是,当令牌不足时,acquire方法并不会阻塞本次调用,而是会算在下次调用的头上。
比如第一次调用时,令牌桶中并没有令牌,但是第一次调用也没有阻塞,而是在第二次调用的时候阻塞了1秒。
也就是说,每次调用欠的令牌(如果桶中令牌不足)都是让下一次调用买单。
https://blog.csdn.net/fanrenxiang/article/details/80949079
Quick Guide to the Guava RateLimiter | Baeldung
原理
Guava的RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。
[image:9A478AC0-7A3D-4615-8C13-414D39F053C5-35974-0002F48FC4E6F157/153F211F-1D78-4444-B6C6-8A0D527AA49B.png]
以SmoothBursty 为例,SmoothBursty中的几个关键字段:
// 桶中最多存放多少秒的令牌数
final double maxBurstSeconds;
//桶中的令牌个数
double storedPermits;
//桶中最多能存放多少个令牌,=maxBurstSeconds*每秒生成令牌个数
double maxPermits;
//加入令牌的平均间隔,单位为微秒,如果加入令牌速度为每秒5个,则该值为200ms
double stableIntervalMicros;
//下一个请求需要等待的时间
private long nextFreeTicketMicros = 0L;
获取令牌acquire函数如下所示,它会调用reserve函数计算获取目标令牌数所需等待的时间,然后使用SleepStopwatch进行休眠,最后返回等待时间。
public double acquire(int permits) {
// 计算获取令牌所需等待的时间
long microsToWait = reserve(permits);
// 进行线程sleep
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
final long reserve(int permits) {
checkPermits(permits);
// 由于涉及并发操作,所以使用synchronized进行并发操作
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
final long reserveAndGetWaitLength(int permits, long nowMicros) {
// 计算从当前时间开始,能够获取到目标数量令牌时的时间
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
// 两个时间相减,获得需要等待的时间
return max(momentAvailable - nowMicros, 0);
}
可以看出主要逻辑都在reserveEarliestAvailable方法中;
reserveEarliestAvailable是刷新令牌数和下次获取令牌时间nextFreeTicketMicros的关键函数。
它有三个步骤:
- 一是调用resync函数增加令牌数,
- 二是计算预支付令牌所需额外等待的时间,
- 三是更新下次获取令牌时间nextFreeTicketMicros和存储令牌数storedPermits。
这里涉及RateLimiter的一个特性,也就是可以预先支付令牌,并且所需等待的时间在下次获取令牌时再实际执行。详细的代码逻辑的解释请看注释。
@Override
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
// 刷新令牌数,相当于每次acquire时在根据时间进行令牌的刷新
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
// 目前即可得到的令牌数=min(请求目标令牌数,当前已有的令牌数)
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
// 需要预先支付的令牌=目标令牌数-目前即可得到的令牌数
double freshPermits = requiredPermits - storedPermitsToSpend;
//watiMicros=预先支付的令牌所需等待的时间=需要预先支付的令牌*加入令牌的平均间隔
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
// 更新nextFreeTicketMicros,本次预先支付的令牌所需等待的时间让下一次请求来实际等待。
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
// 更新令牌数,最低数量为0
this.storedPermits -= storedPermitsToSpend;
// 返回旧的nextFreeTicketMicros数值,无需为预支付的令牌多加等待时间。
return returnValue;
}
//storedPermitsToWaitTime在SmoothWarmingUp和SmoothBuresty的实现不同,用于实现预热缓冲期
//SmoothBuresty 实现直接返回0
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
return 0L;
}
//刷新令牌数storedPermits和下次请求需要等待的时间nextFreeTicketMicros
void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
if (nowMicros > nextFreeTicketMicros) {
//获取新增令牌数=(当前时间-下次请求需要等待的时间)/加入令牌的平均间隔
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
double coolDownIntervalMicros() {
return stableIntervalMicros;
}
其中 ::double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();::
就是新增令牌的核心逻辑。当前时间大于nextFreeTicketMicros时进行刷新,否则直接返回。
下面我们举个例子,让大家更好的理解resync和reserveEarliestAvailable函数的逻辑。
比如RateLimiter的stableIntervalMicros为500,也就是1秒发两个令牌,storedPermits为0,
nextFreeTicketMicros为155391849 5748。
线程一acquire(2),当前时间为155391849 6248,首先resync函数计算,(155391849 6248 - 155391849 5748)/500 = 1,所以当前可获取令牌数为1,但是由于可以预支付,所以
nextFreeTicketMicros= nextFreeTicketMicro + 1 * 500 = 155391849 6748。线程一无需等待。
线程二也来acquire(2),首先resync函数发现当前时间早于nextFreeTicketMicros,所以无法增加令牌数,所以需要预支付2个令牌, nextFreeTicketMicros= nextFreeTicketMicro + 2 * 500 = 155391849 7748。
线程二需要等待155391849 6748时刻,也就是线程一获取时计算的nextFreeTicketMicros时刻。
同样的,线程三获取令牌时也需要等待到线程二计算的nextFreeTicketMicros时刻。