Guava中的RateLimiter實現令牌桶的技巧
看一下網絡上面找的一張圖:
理解起來不難,但是如何用代碼來實現呢,借鑑Guava中的RateLimiter源碼,我們來看一下核心部分:
一些參數說明
/**
* 添加令牌時間間隔
*/
double stableIntervalMicros;
/**
* 下一次請求可以獲取令牌的起始時間
* 由於RateLimiter允許預消費,上次請求預消費令牌後
* 下次請求需要等待相應的時間到nextFreeTicketMicros時刻纔可以獲取令牌
*/
private long nextFreeTicketMicros = 0L; // could be either in the past or future
nextFreeTicketMicros 屬性就是說,會一直睡眠到nextFreeTicketMicros時間到了才返回
創建部分:
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
默認實例化SmoothBursty類,也就是穩定生成令牌的類。
核心函數:
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;
}
}
發現原來RateLimiter不是啓動一個線程,不斷往桶裏加令牌,而是調用的時候才往裏面加,其實現思路爲,若當前時間晚於nextFreeTicketMicros,則計算該段時間內可以生成多少令牌。
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
resync(nowMicros);
long returnValue = nextFreeTicketMicros; // 返回的是上次計算的nextFreeTicketMicros
double storedPermitsToSpend = min(requiredPermits, this.storedPermits); // 可以消費的令牌數
double freshPermits = requiredPermits - storedPermitsToSpend; // 還需要的令牌數
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros); // 根據freshPermits計算需要等待的時間
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros); // 本次計算的nextFreeTicketMicros不返回
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
計算可以生成的令牌數是否比需要的令牌數多
public double acquire(int permits) {
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
如果需要的令牌數比可以生成的令牌數多,則會睡眠一段時間,這段時間在上面的代碼中已經計算出來了。反之,則直接返回。