深入理解RateLimiter
概要
為了對系統(tǒng)資源的保護或者在網(wǎng)關(guān)限制流量,我們一般用到限流算法。Google開源工具包Guava提供了限流工具類RateLimiter,該類基于令牌桶算法實現(xiàn)流量限制,使用十分方便。
RateLimiter原理分析
令牌桶算法
令牌桶算法的原理是系統(tǒng)會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當(dāng)桶里沒有令牌可取時,則拒絕服務(wù)。

實現(xiàn)原理
RateLimiter有兩種限流模式,一種為穩(wěn)定模式(SmoothBursty:令牌生成速度恒定),一種為漸進模式(SmoothWarmingUp:令牌生成速度緩慢提升直到維持在一個穩(wěn)定值)。以下代碼基于基本 guava:26.0-jre。
看看關(guān)鍵類的繼承圖

SmoothBursty
限流效果
先看看效果,對這個工具有一個感性認(rèn)識


RateLimiter.create(5.0) 表示每秒產(chǎn)生5個令牌。輸出的意思是這次獲取令牌所需要等待的時間。
屬性
繼承自SmoothRateLimiter的有以下屬性
/**?The?currently?stored?permits.?*/
??double?storedPermits;
??/**?The?maximum?number?of?stored?permits.?*/
??double?maxPermits;
??/**
???*?The?interval?between?two?unit?requests,?at?our?stable?rate.?E.g.,?a?stable?rate?of?5?permits
???*?per?second?has?a?stable?interval?of?200ms.
???*/
??double?stableIntervalMicros;
??/**
???*?The?time?when?the?next?request?(no?matter?its?size)?will?be?granted.?After?granting?a?request,
???*?this?is?pushed?further?in?the?future.?Large?requests?push?this?further?than?small?requests.
???*/
??private?long?nextFreeTicketMicros?=?0L;?//?could?be?either?in?the?past?or?future
storedPermits - 當(dāng)前桶里有多少令牌。maxPermits - 桶可以最大存儲多少令牌。stableIntervalMicros - 生成一個令牌的間隔,單位微秒。nextFreeTicketMicros - 這個比較難理解,也是關(guān)鍵,意思是下一個請求允許獲取到令牌的微秒數(shù)。
初始化
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的兩個構(gòu)造參數(shù),一個是stopwatch,這個類的作用是能夠獲取從初始化時到現(xiàn)在的時間,另一個參數(shù) maxBurstSeconds是 hard code 為 1。
public?final?void?setRate(double?permitsPerSecond)?{
????checkArgument(
????????permitsPerSecond?>?0.0?&&?!Double.isNaN(permitsPerSecond),?"rate?must?be?positive");
????//這里用了 synchronized 鎖,鎖的范圍是這個 rateLimiter 實例。
????synchronized?(mutex())?{
??????doSetRate(permitsPerSecond,?stopwatch.readMicros());
????}
??}
??
???@Override
??final?void?doSetRate(double?permitsPerSecond,?long?nowMicros)?{
????//resync 方法,它的作用是計算 storedPermits,等下會講到;
????resync(nowMicros);
????//計算 stableIntervalMicros,單位是微秒,用1秒?/?入?yún)⒌牧钆茢?shù),意思就是每多少微秒生成一個令牌;
????double?stableIntervalMicros?=?SECONDS.toMicros(1L)?/?permitsPerSecond;
????this.stableIntervalMicros?=?stableIntervalMicros;
????doSetRate(permitsPerSecond,?stableIntervalMicros);
??}
setRate方法用來初始化令牌生成速率;
@Override
????void?doSetRate(double?permitsPerSecond,?double?stableIntervalMicros)?{
??????double?oldMaxPermits?=?this.maxPermits;
??????//這里涉及一個重要的屬性?maxPermits,它表示桶最大的存儲令牌的數(shù)量,注意maxBurstSeconds?hard?code為1
??????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;
??????}
????}
doSetRate 是模版方法,我們先看 SmoothBursty 的,等下講到 SmoothWarmingUp 時會講它的 doSetRate。這個方法有兩個地方用到,一是初始化時,二是調(diào)用 RateLimiter 的實例方法 setRate 動態(tài)調(diào)整速率時。
延遲計算
初始化就這么簡單了。可能有人在想既然是令牌桶算法,應(yīng)該有個類似定時器的東東來持續(xù)往桶放令牌才對啊,我剛開始也是這么想的,看了代碼覺得自己還是太嫩了,如果開啟一個定時器無可厚非,但如果系統(tǒng)需要N個不同速率的桶來針對不同的場景或用戶,就會極大的消耗系統(tǒng)資源。
RateLimiter用了一種類似于延遲計算的方法,把桶里令牌數(shù)量的計算放在下一個請求中計算,即桶里的令牌數(shù) storedPermits 不是實時更新的,而是等到下一個請求過來時才更新的,具體我們來看看消費令牌的過程。
獲取令牌acquire
主要有兩個方法,一是 acquire,一是 tryAcquire。區(qū)別是如果桶里沒有令牌,前者會阻塞,后者會直接返回 false。我們先看看 acquire 方法
@CanIgnoreReturnValue
??//這個方法主要是獲取令牌的同時,返回需要等待的時間,主要就是reserve方法,至于 stopwatch.sleepMicrosUninterruptibly 大家理解為 sleep 就好了。
??public?double?acquire(int?permits)?{
????long?microsToWait?=?reserve(permits);
????stopwatch.sleepMicrosUninterruptibly(microsToWait);
????return?1.0?*?microsToWait?/?SECONDS.toMicros(1L);
??}
??
?final?long?reserve(int?permits)?{
????checkPermits(permits
????//這里用 synchronized 鎖,所以下面的邏輯大家不用考慮由并發(fā)產(chǎn)生的問題;
????synchronized?(mutex())?{
??????//stopwatch.readMicros()?的作用是獲取從初始化到現(xiàn)在的系統(tǒng)時間微秒數(shù)。
??????return?reserveAndGetWaitLength(permits,?stopwatch.readMicros());
????}
??}
??
??//獲取令牌并等待
??final?long?reserveAndGetWaitLength(int?permits,?long?nowMicros)?{
????long?momentAvailable?=?reserveEarliestAvailable(permits,?nowMicros);
????return?max(momentAvailable?-?nowMicros,?0);
??}
reserveEarliestAvailable 是整個 RateLimiter 的核心方法,它是 SmoothRateLimite 的一個模板方法。
@Override
??final?long?reserveEarliestAvailable(int?requiredPermits,?long?nowMicros)?{
????resync(nowMicros);
????long?returnValue?=?nextFreeTicketMicros;
????//storedPermitsToSpend 是可以消費的令牌數(shù),最多也就取 storedPermits 這么多了;
????double?storedPermitsToSpend?=?min(requiredPermits,?this.storedPermits);
????//freshPermits 字面意思新鮮的令牌,我們理解為還沒生成的或者將來會生成的令牌。假如我要10個令牌,但是桶里現(xiàn)在只有5個令牌,那么 freshPermits 值為 5 = 10?- 5;
????double?freshPermits?=?requiredPermits?-?storedPermitsToSpend;
????//然后就通過 freshPermits 計算出需要等待的時間 waitMicros。storedPermitsToWaitTime 是一個模板方法,對 SmoothBursty 來說這個沒啥用,它始終返回?0,所以 waitMicros = freshPermits * stableIntervalMicros;
????long?waitMicros?=
????????storedPermitsToWaitTime(this.storedPermits,?storedPermitsToSpend)
????????????+?(long)?(freshPermits?*?stableIntervalMicros);
????//更新 nextFreeTicketMicros,追加 waitMicros;
????this.nextFreeTicketMicros?=?LongMath.saturatedAdd(nextFreeTicketMicros,?waitMicros);
????//storedPermits 追減用掉的令牌;
????this.storedPermits?-=?storedPermitsToSpend;
????return?returnValue;
??}
reserveEarliestAvailable的返回值,注意了,這里返回的是更新前的 nextFreeTicketMicros,也就是上一個請求更新的 nextFreeTicketMicros。那么這個 waitMicros 等待時間也不是當(dāng)前請求需要等待的時間,而是下一個請求需要等待的時間,這個涉及到 RateLimiter 一個很重要的設(shè)計理念,就是“預(yù)消費”,通俗點理解即“前人消費,后人買單”,理解好這點,是使用和理解 RateLimiter 的關(guān)鍵。
我舉一個例子來助于理解,桶的速率為每秒產(chǎn)生5個令牌,現(xiàn)在桶里有4個令牌,現(xiàn)在過來一個請求需要10個令牌,那么這個請求會被無阻塞允許,不需要等待,同時又過來一個請求,現(xiàn)在桶里已經(jīng)沒有令牌了,而且上一個請求還“欠下”6個令牌,那么這個請求需要等待 (10 - 4) / 5 秒的時間,才被允許執(zhí)行。
//剛剛說的延遲計算令牌數(shù)就在這里。這個方法是用來計算 storedPermits (桶里的令牌數(shù)),nowMicros 是當(dāng)前的微秒數(shù),nextFreeTicketMicros 上面說過了。
void?resync(long?nowMicros)?{
????//?if?nextFreeTicket?is?in?the?past,?resync?to?now
????if?(nowMicros?>?nextFreeTicketMicros)?{
??????double?newPermits?=?(nowMicros?-?nextFreeTicketMicros)?/?coolDownIntervalMicros();
??????//令牌數(shù)不能超過 maxPermits;?
??????storedPermits?=?min(maxPermits,?storedPermits?+?newPermits);
??????//把 nextFreeTicketMicros 置為當(dāng)前時間。??
??????nextFreeTicketMicros?=?nowMicros;
????}
??}
??
??//coolDownIntervalMicros?是一個模板方法,看?SmoothBursty?的,值等于?stableIntervalMicros
??@Override
????double?coolDownIntervalMicros()?{
??????return?stableIntervalMicros;
????}
所以resync的意思就是如果當(dāng)前時間大于 nextFreeTicketMicros,就用當(dāng)前時間 - nextFreeTicketMicros / 每 stableIntervalMicros 生成一個令牌,即這個時間差可以生成多少個令牌;
我用一個圖來表示會更加清晰

為什么要“預(yù)消費”
RateLimiter 它是這樣想的:
Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently completely unused, and an expensive acquire(100) request comes. It would be nonsensical to just wait for 100 seconds, and /then/ start the actual task. Why wait without doing anything? A much better approach is to /allow/ the request right away (as if it was an acquire(1) request instead), and postpone /subsequent/ requests as needed. In this version, we allow starting the task immediately, and postpone by 100 seconds future requests, thus we allow for work to get done in the meantime instead of waiting idly.
大概意思是,假設(shè)令牌產(chǎn)生的速率為1秒一個,系統(tǒng)平時是很空閑的,突然來了一個 expensive acquire(100) 的請求,難道我要瞎等100秒才執(zhí)行嗎?這毫無意義,不能充分利用資源啊,所以干脆可以直接允許好了,不要做無謂的等待。簡單來說就是為了突發(fā)性。
消費場景分析
我們分情況分析一下就清楚了:
nowMicros > nextFreeTicketMicros 這種場景發(fā)生在剛初始化時,或者桶里的令牌還有剩余。 如果請求所需令牌 < 桶里的 即桶里令牌滿足這次消費的話,那么 nextFreeTicketMicros 會移動到 nowMicros 的位置 令牌數(shù) storedPermits = 原來 - 消費的 + 這段時間增加的。 
如果請求所需令牌 >= 桶里的 這時會優(yōu)先把桶里的令牌全部拿走,那么 storedPermits 就等于0了。如果還不夠,就會發(fā)生預(yù)消費,那么 nextFreeTicketMicros 會后移,移動多少?就是需要產(chǎn)生“溢出”令牌數(shù)的時間。 
nowMicros < nextFreeTicketMicros 在上面有一個場景 nextFreeTicketMicros 會后移,移動了多少不知道,要看上一個請求,那么如果這段時間內(nèi)有請求過來呢?這時當(dāng)前的請求就要為上一個請求“買單”了,它需要等待到 nextFreeTicketMicros 這個時刻才能允許執(zhí)行,但此時桶里令牌數(shù)是 0 的,所以這個請求也是會預(yù)消費的。
SmoothWarmingUp
SmoothBursty 是以一個固定的速率來產(chǎn)生令牌的,它具有突發(fā)性,這個可能適用大多數(shù)場景。而 SmoothWarmingUp 考慮的是譬如一個系統(tǒng)剛啟動,但如果這時有大量請求過來,因為突發(fā)性,這些請求都會被允許,但此時系統(tǒng)可能沒有那么多資源去響應(yīng),所以需要一個“熱身”時間,SmoothWarmingUp 就派上用場了。它跟 SmoothBursty 的大概思路都是差不多的,只是個別地方有差別,主要就是之前提到幾個模板方法,我們來看看。
限流效果


SmoothWarmingUp 的效果是剛開始產(chǎn)生令牌的速率比較慢,隨著請求過來,會進入“熱身”期,速率逐漸提升到 permitsPerSecond 這個速度;但是如果沒有請求了,又會“冷卻”下去,請求過來又要從“熱身”開始。
初始化
初始化也是調(diào)用 create,不過參數(shù)列表有點不同
//permitsPerSecond 是“熱身”后的穩(wěn)定速率;???
//warmupPeriod 是“熱身”時間,如果這段時間內(nèi)持續(xù)有請求過來消費令牌,就會達(dá)到一個穩(wěn)定的速率,這時跟 SmoothBursty 效果一樣;???
//unit 是 warmupPeriod 的單位;??
public?static?RateLimiter?create(double?permitsPerSecond,?long?warmupPeriod,?TimeUnit?unit)?{
????checkArgument(warmupPeriod?>=?0,?"warmupPeriod?must?not?be?negative:?%s",?warmupPeriod);
????//coldFactor hard code 為 3;
????return?create(
????????permitsPerSecond,?warmupPeriod,?unit,?3.0,?SleepingStopwatch.createFromSystemTimer());
??}
??
??@VisibleForTesting
??static?RateLimiter?create(
??????double?permitsPerSecond,
??????long?warmupPeriod,
??????TimeUnit?unit,
??????double?coldFactor,
??????SleepingStopwatch?stopwatch)?{
????RateLimiter?rateLimiter?=?new?SmoothWarmingUp(stopwatch,?warmupPeriod,?unit,?coldFactor);
????rateLimiter.setRate(permitsPerSecond);
????return?rateLimiter;
??}
??
??SmoothWarmingUp(
????????SleepingStopwatch?stopwatch,?long?warmupPeriod,?TimeUnit?timeUnit,?double?coldFactor)?{
??????super(stopwatch);
??????this.warmupPeriodMicros?=?timeUnit.toMicros(warmupPeriod);
??????this.coldFactor?=?coldFactor;
????}
“熱身”速率函數(shù)及說明
由于接下來涉及到一些計算,我們先看看“熱身”函數(shù)的定義及圖像
/**
???*?This?implements?the?following?function?where?coldInterval?=?coldFactor?*?stableInterval.
???*
???*?
???*??????????^?throttling
???*??????????|
???*????cold??+??????????????????/
???*?interval?|?????????????????/.
???*??????????|????????????????/?.
???*??????????|???????????????/??.???←?"warmup?period"?is?the?area?of?the?trapezoid?between
???*??????????|??????????????/???.?????thresholdPermits?and?maxPermits
???*??????????|?????????????/????.
???*??????????|????????????/?????.
???*??????????|???????????/??????.
???*???stable?+----------/??WARM?.
???*?interval?|??????????.???UP??.
???*??????????|??????????.?PERIOD.
???*??????????|??????????.???????.
???*????????0?+----------+-------+--------------→?storedPermits
???*??????????0?thresholdPermits?maxPermits
???*?
???*
???*?Before?going?into?the?details?of?this?particular?function,?let's?keep?in?mind?the?basics:
???*
???*?
???*???- The?state?of?the?RateLimiter?(storedPermits)?is?a?vertical?line?in?this?figure.
???*??? - When?the?RateLimiter?is?not?used,?this?goes?right?(up?to?maxPermits)
???*??? - When?the?RateLimiter?is?used,?this?goes?left?(down?to?zero),?since?if?we?have
???*???????storedPermits,?we?serve?from?those?first
???*??? - When?_unused_,?we?go?right?at?a?constant?rate!?The?rate?at?which?we?move?to?the?right?is
???*???????chosen?as?maxPermits?/?warmupPeriod.?This?ensures?that?the?time?it?takes?to?go?from?0?to
???*???????maxPermits?is?equal?to?warmupPeriod.
???*??? - When?_used_,?the?time?it?takes,?as?explained?in?the?introductory?class?note,?is?equal?to
???*???????the?integral?of?our?function,?between?X?permits?and?X-K?permits,?assuming?we?want?to
???*???????spend?K?saved?permits.
???*?
???*
???*?In?summary,?the?time?it?takes?to?move?to?the?left?(spend?K?permits),?is?equal?to?the?area?of
???*?the?function?of?width?==?K.
首先不要被嚇到,還是很簡單的,我來說明一下。x 軸是 storedPermits,即桶里的令牌數(shù)。軸上主要刻有兩個值,一是thresholdPermits,這個等下會講到;一個是maxPermits;y 軸是生成一個令牌的間隔,單位微秒。軸上主要刻有兩個值,一是stable interval;一個是 cold interval,coldInterval = coldFactor * stableInterval,由于 coldFactor hard code 為 3,所以 coldInterval 等于3倍的 stable interval。warmup period 是入?yún)⒌摹盁嵘怼睍r間。由這幾個值構(gòu)成的左邊的長方形和右邊的梯形。由于 x 軸是令牌數(shù),y 軸是生成令牌的間隔,所以它們的乘積是一個時間。
doSetRate方法
@Override
????void?doSetRate(double?permitsPerSecond,?double?stableIntervalMicros)?{
??????double?oldMaxPermits?=?maxPermits;
??????//coldIntervalMicros -?固定 stableIntervalMicros * 3,這里的stableIntervalMicros跟SmoothBursty一樣。
??????double?coldIntervalMicros?=?stableIntervalMicros?*?coldFactor;
??????//thresholdPermits -?桶里令牌數(shù)的閾值,低于這個值之后就會進入穩(wěn)定速率期;但高于這個值,又會回到“熱身”期。
??????thresholdPermits?=?0.5?*?warmupPeriodMicros?/?stableIntervalMicros;
??????//maxPermits?-?意思也是桶里允許最多的令牌
??????maxPermits?=
??????????thresholdPermits?+?2.0?*?warmupPeriodMicros?/?(stableIntervalMicros?+?coldIntervalMicros);
??????//slope -?斜率,就是你在圖形上看到那條斜線的斜率,這時用來方便已知 storedPermits 時,求出當(dāng)前的 coldIntervalMicros。條件都已知了,斜率的計算不多說。??
??????slope?=?(coldIntervalMicros?-?stableIntervalMicros)?/?(maxPermits?-?thresholdPermits);
??????if?(oldMaxPermits?==?Double.POSITIVE_INFINITY)?{
????????//?if?we?don't?special-case?this,?we?would?get?storedPermits?==?NaN,?below
????????storedPermits?=?0.0;
??????}?else?{
????????//還有一點注意,這里初始化時,桶里的令牌數(shù)為滿,跟 SmoothBursty 不一樣。
????????storedPermits?=
????????????(oldMaxPermits?==?0.0)
??????????????????maxPermits?//?initial?state?is?cold
????????????????:?storedPermits?*?maxPermits?/?oldMaxPermits;
??????}
????}
這里對幾個參數(shù)的計算說明一下:
thresholdPermits 為什么 thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros?先看看官方的注釋
Assuming we have saturated demand, the time to go from maxPermits to thresholdPermits is equal to warmupPeriod. And the time to go from thresholdPermits to 0 is warmupPeriod/2. (The reason that this is warmupPeriod/2 is to maintain the behavior of the original implementation where coldFactor was hard coded as 3.)
根據(jù)官方的注釋,說“熱身”的時間是穩(wěn)定時間的2倍(我這里表述不準(zhǔn)確),即梯形面積為長方形面積的2倍,要保持跟 coldFactor 寫死為3一樣,原因是希望令牌速率提升的幅度跟它所需要的時間的比例保持一致(這點我不知道理解的對不對,希望有人幫我佐證) 因為梯形面積是已知的,又知道長方形的面積和一條邊長,容易求得 thresholdPermits。
maxPermits 為什么 maxPermits = thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros) ?這個簡單,利用梯形面積公式求出高,然后再加上 thresholdPermits。
消費令牌的主要邏輯在 reserveEarliestAvailable 方法,里面有一個模板方法 storedPermitsToWaitTime,我們看看 SmoothWarmingUp 的實現(xiàn)。
@Override
????long?storedPermitsToWaitTime(double?storedPermits,?double?permitsToTake)?{
??????//availablePermitsAboveThreshold 表示多于 thresholdPermits 的可用令牌數(shù);
??????double?availablePermitsAboveThreshold?=?storedPermits?-?thresholdPermits;
??????long?micros?=?0;
??????//?measuring?the?integral?on?the?right?part?of?the?function?(the?climbing?line)
??????//如果 availablePermitsAboveThreshold >?0,說明還在“熱身”期,令牌的數(shù)量需要控制在 thresholdPermits;
??????if?(availablePermitsAboveThreshold?>?0.0)?{
????????//permitsAboveThresholdToTake 表示這次允許取的最大的令牌數(shù);
????????double?permitsAboveThresholdToTake?=?min(availablePermitsAboveThreshold,?permitsToTake);
????????//?TODO(cpovirk):?Figure?out?a?good?name?for?this?variable.
????????//length 表示在大梯形中,以 permitsToTime(availablePermitsAboveThreshold)?為右邊的底部,以 permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake)?為左邊的底部,構(gòu)成的小梯形中,這兩條邊的和,用于下面的計算;
????????double?length?=
????????????permitsToTime(availablePermitsAboveThreshold)
????????????????+?permitsToTime(availablePermitsAboveThreshold?-?permitsAboveThresholdToTake);
????????//micros 就是計算小梯形的面積了,permitsAboveThresholdToTake 就是這個小梯形的高了;
????????micros?=?(long)?(permitsAboveThresholdToTake?*?length?/?2.0);
????????permitsToTake?-=?permitsAboveThresholdToTake;
??????}
??????//?measuring?the?integral?on?the?left?part?of?the?function?(the?horizontal?line)
??????//如果多于 thresholdPermits 的令牌數(shù)不夠,那么就會進入穩(wěn)定期,使用穩(wěn)定的速率。
??????micros?+=?(long)?(stableIntervalMicros?*?permitsToTake);
??????return?micros;
????}
????
????//permitsToTime 就是利用斜率求出 y 軸的值。
????private?double?permitsToTime(double?permits)?{
??????return?stableIntervalMicros?+?permits?*?slope;
????}
看一下圖像就清楚了

從 storedPermitsToWaitTime 看出,SmoothWarmingUp 會優(yōu)先取出超過 thresholdPermits 的令牌,但即使有令牌可用,還是會阻塞請求,以這樣來防止啟動時的突發(fā)性。隨著請求增加,令牌的減少,桶的令牌會達(dá)到 thresholdPermits,這時就相當(dāng)于“熱身”完了,跟 SmoothBursty 一樣。但如果一直沒有請求來消費令牌,令牌數(shù)增加,就會從新進去“熱身”期了。
coolDownIntervalMicros
在 resync 方法中,還有一個模板方法 coolDownIntervalMicros,在 SmoothWarmingUp 的實現(xiàn)中為
@Override
????double?coolDownIntervalMicros()?{
??????return?warmupPeriodMicros?/?maxPermits;
????}
這個方法是用于得出從上一個請求到當(dāng)請求的時間內(nèi),可以生成令牌的時間間隔,在 SmoothBursty 的實現(xiàn)中它就是 stableIntervalMicros。但在這里我不明白為什么要這樣計算(梯形面積 / maxPermits 得出是什么???),如果有人知道,希望你留言告知我這個數(shù)學(xué)渣。
setRate的公平性考慮
RateLimiter 可以動態(tài)調(diào)整產(chǎn)生令牌的速率,但是這里涉及一個問題,如何處理當(dāng)前被阻塞的請求以及后續(xù)請求?先看看官方的注釋:
/**
???*?Updates?the?stable?rate?of?this?{@code?RateLimiter},?that?is,?the?{@code?permitsPerSecond}
???*?argument?provided?in?the?factory?method?that?constructed?the?{@code?RateLimiter}.?Currently
???*?throttled?threads?will?not?be?awakened?as?a?result?of?this?invocation,?thus?they?do?not
???*?observe?the?new?rate;?only?subsequent?requests?will.
???*
???*?Note?though?that,?since?each?request?repays?(by?waiting,?if?necessary)?the?cost?of?the
???*?previous?request,?this?means?that?the?very?next?request?after?an?invocation?to?{@code
???*?setRate}?will?not?be?affected?by?the?new?rate;?it?will?pay?the?cost?of?the?previous?request,
???*?which?is?in?terms?of?the?previous?rate.
???*
???*?
The?behavior?of?the?{@code?RateLimiter}?is?not?modified?in?any?other?way,?e.g.?if?the?{@code
???*?RateLimiter}?was?configured?with?a?warmup?period?of?20?seconds,?it?still?has?a?warmup?period?of
???*?20?seconds?after?this?method?invocation.
注釋的意思說了當(dāng)前被阻塞的線程不會因此醒過來,它們對速率的改變沒有感知,接下來的請求才會適應(yīng)新的速率。
Note though that, since each request repays (by waiting, if necessary) the cost of the previous request, this means that the very next request after an invocation to {@code setRate} will not be affected by the new rate; it will pay the cost of the previous request, which is in terms of the previous rate.
其中這句話不好理解,我的理解是,假設(shè)速率降低了,如果需要對當(dāng)前被阻塞的請求做調(diào)整的話,那么它們的阻塞時間會增加(這里假設(shè)的結(jié)果是增加),由于連鎖反應(yīng),最后導(dǎo)致 nextFreeTicketMicros 會后移,這就對于改變速率后的請求不公平了。所以 RateLimiter 的做法是當(dāng)前阻塞的請求還是按照原來時間等待,后續(xù)的請求用新的速率,這樣實現(xiàn)也比較簡單,對后續(xù)的請求也公平。
tryAcquire
補充說明一下tryAcquire,這方法實際應(yīng)用比acquire 方法還要實用。
public?boolean?tryAcquire(int?permits,?long?timeout,?TimeUnit?unit)?{
????long?timeoutMicros?=?max(unit.toMicros(timeout),?0);
????checkPermits(permits);
????long?microsToWait;
????synchronized?(mutex())?{
??????long?nowMicros?=?stopwatch.readMicros();
??????if?(!canAcquire(nowMicros,?timeoutMicros))?{
????????return?false;
??????}?else?{
????????microsToWait?=?reserveAndGetWaitLength(permits,?nowMicros);
??????}
????}
????stopwatch.sleepMicrosUninterruptibly(microsToWait);
????return?true;
??}
??
??//判斷就是 canAcquire 方法,很簡單,就是判斷 nextFreeTicketMicros 的位置,因為你最多也就需要等待到 nextFreeTicketMicros 這么長的時間嘛。
??private?boolean?canAcquire(long?nowMicros,?long?timeoutMicros)?{
????return?queryEarliestAvailable(nowMicros)?-?timeoutMicros?<=?nowMicros;
??}
??
??@Override
??final?long?queryEarliestAvailable(long?nowMicros)?{
????return?nextFreeTicketMicros;
??}
tryAcquire 會先去判斷是否能夠在 timeout 的等待時間內(nèi)能夠獲取到令牌,如果可以就阻塞等待,如果不能則直接返回false。
總結(jié)
Guava 的 RateLimiter 是一個高效低耗,簡單易用,優(yōu)秀的限流工具,它基于令牌桶算法,并且提供了一個很好的實現(xiàn)參考。
參考資料
[https://blog.wangqi.love/articles/Java/Guava%20RateLimiter%E5%88%86%E6%9E%90.html](https://blog.wangqi.love/articles/Java/Guava RateLimiter分析.html) https://segmentfault.com/a/1190000012875897 https://www.jianshu.com/p/3dfae5c15eb9
來源:http://r6d.cn/TsMv
版權(quán)申明:內(nèi)容來源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無法確認(rèn),我們都會標(biāo)明作者及出處,如有侵權(quán)煩請告知,我們會立即刪除并表示歉意。謝謝!

