今天看了下Guava RateLimiter 的源码,简单记录下
时间窗口内限流常用算法有: 计数,令牌桶和漏桶算法。RateLimiter算是令牌桶算法吧。
刚开始我以为是使用一个单独的线程在指定时间间隔生产令牌存入桶中,获取时就去减少令牌,当没有可用令牌时就Blocking线程直到生产线程唤醒(类似阻塞队列原理)。看了源码发现Guava 使用了更加简单高效的方式完成了令牌桶限流功能。这里介绍相对简单的实现类SmoothBursty(还有一个实现类 SmoothWarmingUp)
使用方式:
//每妙生产一个令牌
RateLimiter rateLimiter = RateLimiter.create(1);
//获取一个令牌
rateLimiter.acquire();
主要盾下acquire 方法的整个流程:
public double acquire() {
return acquire(1);
}
public double acquire(int permits) {
//获取令牌等待时间
long microsToWait = reserve(permits);
//等待
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
final long reserve(int 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);
}
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//刷新令牌数和nextFreeTicketMicros
resync(nowMicros);
//上次获取令牌算出下次获取令牌需等待时长,返回
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//本次需要的令牌数 - 当前最多所能提供令牌数 = 预先支配的令牌数。为计算下次获取令牌等待时间
double freshPermits = requiredPermits - storedPermitsToSpend;
//计算预支付令牌所要等待时间。storedPermitsToWaitTime 在SmoothBursty实现直接返回0
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
//下次获取令牌所需要等待的时间
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//扣减令牌
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
void resync(long nowMicros) {
//如果当前时间大于 nextFreeTicketMicros 就刷新令牌数。
if (nowMicros > nextFreeTicketMicros) {
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
//将当前时间设置为nextFreeTickemicros
nextFreeTicketMicros = nowMicros;
}
以上代码就完成了流量的控制。
过程分析
我们假设1秒钟生产一个令牌,当有3个线程(t1,t2,t3),在 9:00:00 时刻请求令牌会是什么情况?(假定第一个线程是0.0秒时请求,线程二0.2秒时请求,线程三0.3妙时请求。每次只获取一个令牌)
我们分析下:
t1 获可直接获取到令牌(预先支付机制)。 t1 这次获取过程中不会生产 Premits,因为nowMicros 大于 dnextFreeTicketMicros不成立,这时会预先支付1一个令牌,那么算出下一次获取令牌等待时刻为 9:00:01。(预支付令牌数 * 令牌生产间隔数)
t2 获取令牌时将要等待到9:00:01,因为 t1 在获取令牌时算出的 nextFreeTicketMicros 为1秒后(本次等待时间是上次获取令牌计算出的等待时间nextFreeTicketMicros )。t2 这次获取过程中也不会生产Premits. 所以也是预先支付1一个令牌,算出下一次获取令牌时刻为9:00:02
t3 获取令牌时要等到9:00:02 ,过程和 t2 一样。依此类推