rateLimiter是一种平滑突发限流器,桶的容量没有限制,在桶中存有令牌的时候允许突发的流量激增时刻。
看代码之前需要记住几个变量含义:
- storedPermits 桶中的令牌数
- intervalMicros 生成令牌的时间间隔
- nextFreeTicketMicros 下一次请求可以获取令牌的起始时间. 由于RateLimiter允许预消费,上次请求预消费令牌后,下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌.
如初始值nextFreeTicketMicros=0,intervalMicros=5, 如acquire(2) 则会更新到nextFreeTicketMicros+=intervalMicros*acquirePermits == 10
以下根据代码执行路径逐层分析
acuqire 获取令牌
//获取令牌,指定获取令牌数量permits
public double acquire(int permits) {
//计算获取令牌需要多长时间
long microsToWait = reserve(permits);
//睡眠相应的时间后返回表示获取令牌结束
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
reserve 计算获取令牌需要多长时间
final long reserve(int permits) {
//校验permits>0
checkPermits(permits);
//保证并发同步
synchronized (mutex()) {
//计算时间
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return max(momentAvailable - nowMicros, 0);
}
//真正计算时间的逻辑:
//requiredPermits:需要的令牌数
//nowMicros:当前时间
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//重新计算当前已生成的令牌数
resync(nowMicros);
//设置下一次请求可以获取令牌的起始时间为返回值,表示用户需要等待多久
long returnValue = nextFreeTicketMicros;
//计算可直接获得的令牌数,如storedPermits=1,requiredPermits=3, 则storedPermitsToSpend=1
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//计算需要预支的令牌数,则例子中 freshPermits=2
double freshPermits = requiredPermits - storedPermitsToSpend;
//计算预支的令牌数需要等待多久
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
//更新下次令牌获取时间为增加了令牌预支时间的,让下次获取令牌的线程去承担这次令牌预支的后果
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//更新已经生成的令牌数
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
reserveEarliestAvailable 的两种情况:
- nextFreeTicketMicro>now() 表示上次获取令牌有预支行为,本次需要为上次的预支行为买单,等待上次的预支的时间,然后本次获取令牌依旧直接预支,让下次获取令牌为本次买单;
- nextFreeTicketMicro<=now() 表示桶中容有令牌,可直接获取,如容量不够则预支requiredPermits-storedPermits,并设置nextFreeTicketMicro加上预支的时间,让下次获取令牌为本次买单。
resync 重新计算令牌数
void resync(long nowMicros) {
//如果当前时间大于nextFreeTicketMicros时,重新计算桶中的令牌数
if (nowMicros > nextFreeTicketMicros) {
//当前时间和nextFreeTicketMicros的时间间隔 除以 生成令牌的时间间隔
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}