RateLimiter的設(shè)計思想
RateLimiter最大的特點是它可以以一個穩(wěn)定的速率讓線程通過。當(dāng)線程過多的時候环础,它可以通過sleep使線程暫停囚似,從而達到強制控制線程通過速度的目標(biāo)。
控制QPS最簡單的方法是可以通過保留上個線程通過的時間线得,從而可以計算下次請求通過時間饶唤。但是,這樣RateLimiter只知道最后一個線程通過的時間贯钩。如果RateLimiter長時間沒有使用募狂,然后一個請求到達并通過办素,這時RateLimiter會立即忘記在過去很長時間中,RateLimiter并沒有被充分利用祸穷,這會導(dǎo)致RateLimiter利用率不足性穿。由于過去未充分利用RateLimiter,這可能意味著過剩的資源雷滚,RateLimiter應(yīng)該加速一段時間需曾,從而充分利用過剩的資源。
另一方面祈远,過去資源未充分利用可能意味著它的緩存過期胯舷,在預(yù)熱階段發(fā)生很很消耗性能的操作,例如在Executors.newCachedThreadPool所創(chuàng)建的線程池中绊含,線程超過1分鐘未之后桑嘶,就會被銷毀,一個新的任務(wù)需要被執(zhí)行時躬充,需要重新創(chuàng)建線程逃顶。
RateLimiter通過記錄下個線程可以獲取到令牌的時間,從而達到控制線程通過速率的目的充甚。
Guava中RateLimiter有兩種實現(xiàn)SmoothBursty和SmoothWarmingUp以政,這里主要介紹SmoothBursty。
重要屬性介紹
maxBurstSeconds:通過maxBurstSeconds來控制保存的令牌數(shù)量伴找。如果一個RateLimiter是2QPS盈蛮,maxBurstSeconds是10秒,這樣技矮,我們可以最多保存2*10=20個令牌抖誉。
storedPermits:當(dāng)前令牌桶中令牌的個數(shù)。
maxPermits:令牌桶中做多存儲令牌的數(shù)量衰倦。
stableIntervalMicros:生成令牌的時間間隔袒炉。例如,每秒可以生成5個令牌樊零,那stableIntervalMicros是200我磁。
nextFreeTickerMicros:生成下個令牌的時間。
RateLimiter的創(chuàng)建
在RateLimiter中驻襟,通過它的靜態(tài)方法創(chuàng)建SmoothBursty
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
@VisibleForTesting
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
super(stopwatch);
this.maxBurstSeconds = maxBurstSeconds;
}
stopwatch是控制有關(guān)時間和sleep線程的夺艰。
maxBurstSeconds是用來控制保存令牌的數(shù)量的。
public final void setRate(double permitsPerSecond) {
checkArgument(
permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
synchronized (mutex()) {
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
mutex()通過雙檢查鎖始終返回一個同一個單例對象沉衣,從而使方法doSetRate()的線程安全郁副。
stopwatch.readMicros()讀取了當(dāng)前時間。
@Override
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros);
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
resync方法主要用于同步RateLimiter中storedPermits和nextFreeTicketMicros.
stableIntervalMicros是生產(chǎn)一個令牌的所使用的時間厢蒜,比如permitsPerSecond=0.5(每秒鐘生產(chǎn)0.5個令牌)霞势,那生產(chǎn)一個令牌的時間是2秒(2000毫秒)烹植。
doSetRate通過計算斑鸦,計算出storedPermits和maxPermits的值愕贡。
以上方法導(dǎo)致最終的結(jié)果就是storedPermits=0,maxPermits=0巷屿,nextFreeTicketMicros是當(dāng)前的時間固以。
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;
}
}
當(dāng)RateLimiter初始化的時候,nextFreeTicketMicros=0嘱巾。coolDownIntervalMicros方法返回stableIntervalMicros憨琳,在RateLimiter初始化的時候,stableIntervalMicros=0旬昭,所以newPermits是Infinity篙螟。在RateLimiter初始化的時候,maxPermits=0问拘,所以storedPermits=0遍略,表示剛初始化的時候,RateLimiter中沒有緩存的令牌骤坐。
resync還將nextFreeTicketMicros更新為當(dāng)前的時間绪杏。
@Override
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = this.maxPermits;
maxPermits = maxBurstSeconds * permitsPerSecond;
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
// if we don't special-case this, we would get storedPermits == NaN, below
storedPermits = maxPermits;
} else {
storedPermits =
(oldMaxPermits == 0.0)
? 0.0 // initial state
: storedPermits * maxPermits / oldMaxPermits;
}
}
當(dāng)RateLimiter初始化的時候,會走else語句纽绍,因為oldMaxPermits是0蕾久,導(dǎo)致storedPermits=0。
RateLimiter的使用
@CanIgnoreReturnValue
public double acquire(int permits) {
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
reserve方法會計算出當(dāng)前線程下次啟動需要等待的時間拌夏,然后通過sleep將線程暫停僧著,當(dāng)線程重新啟動的時候,返回線程暫停的時間障簿。
final long reserve(int permits) {
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);
}
@Override
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
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主要用于計算當(dāng)前線程獲取到令牌的時間霹抛,在計算的過程中會考慮緩存的令牌。
還會計算下個線程獲取到令牌的時間卷谈,并賦給nextFreeTicketMicros杯拐。