Guava 限流器源码解析
限流器介绍了限流器的概念以及四种主流的限流算法,不过其中的代码样例比较简单,无法在生产环境直接使用,这篇文章介绍下Google开源的Guava中实现的限流器Guava 限流器。
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, RateLimiter.SleepingStopwatch.createFromSystemTimer());
}
// 实际上初始化的是SmoothBursty实例
static RateLimiter create(double permitsPerSecond, RateLimiter.SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0D);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
Guava提供了acquire方法来执行具体的限流操作
public double acquire() {
return this.acquire(1);
}
@CanIgnoreReturnValue
public double acquire(int permits) {
long microsToWait = this.reserve(permits);
this.stopwatch.sleepMicrosUninterruptibly(microsToWait); // 休眠指定的时间,不响应中断
return 1.0D * (double)microsToWait / (double) TimeUnit.SECONDS.toMicros(1L);
}
final long reserve(int permits) {
checkPermits(permits); // 检查传入的参数是否正确
// 临界区,这里如果permits数量能够被满足说明不会触发限流,否则返回的是至少要等待的时间,多少时间后可以被处理
synchronized(this.mutex()) {
return this.reserveAndGetWaitLength(permits, this.stopwatch.readMicros());
}
}
Guava限流器的令牌补充以及计算等待时间等逻辑都是在reserveEarliestAvailable实现的
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
this.resync(nowMicros); //惰性补充令牌
long returnValue = this.nextFreeTicketMicros; // 本次请求的最早可用时间
double storedPermitsToSpend = Math.min((double)requiredPermits, this.storedPermits); // 存量令牌的消耗数
double freshPermits = (double)requiredPermits - storedPermitsToSpend; // 还需要生成多少令牌N才能满足本次请求
long waitMicros = this.storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long)(freshPermits * this.stableIntervalMicros); // 存量令牌消耗完后,N个令牌尚未存在,需等待时间才能生成
this.nextFreeTicketMicros = LongMath.saturatedAdd(this.nextFreeTicketMicros, waitMicros); // 现场生成N个令牌会影响之后请求的最早响应时间,所以更新下次请求的最早可用时间
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
resync补充令牌,Guava是在请求到来时根据上次补充令牌的时间差惰性补充令牌,对CPU比较友好
void resync(long nowMicros) {
if (nowMicros > this.nextFreeTicketMicros) {
double newPermits = (double)(nowMicros - this.nextFreeTicketMicros) / this.coolDownIntervalMicros();
this.storedPermits = Math.min(this.maxPermits, this.storedPermits + newPermits);
this.nextFreeTicketMicros = nowMicros;
}
}
acquire方法是阻塞等待,也就是如果请求没有获取到令牌,会睡眠直到令牌桶中的令牌足够其响应,还有一个非阻塞等待方法tryAcquire。
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
long timeoutMicros = Math.max(unit.toMicros(timeout), 0L);
checkPermits(permits);
long microsToWait;
synchronized(this.mutex()) {
long nowMicros = this.stopwatch.readMicros();
if (!this.canAcquire(nowMicros, timeoutMicros)) { // 比较最早可用时间与超时时间
return false;
}
microsToWait = this.reserveAndGetWaitLength(permits, nowMicros);
}
this.stopwatch.sleepMicrosUninterruptibly(microsToWait);
return true;
}
tryAcquire通过比较令牌的最早可用时间和超时时间,来判断是否可以获取到令牌,如果超时时间内可以获取,则阻塞等待,否则的话直接返回false。
Guava限流器依赖时钟进行令牌以及最早可用时间等参数的计算,因此时钟的精度决定了限流器的限流准确性。由于系统时钟存在回拨、精度低等问题,Guava采用逻辑时钟,即以限流器创建时间为起点,是一个单调递增的时钟,时钟精度达到纳秒级,非常适合限流场景。限流算法必须依赖一个“稳定、单调、高精度”的时间源

浙公网安备 33010602011771号