什么是平滑限流?它相對於固定、滑動窗口限流,它可以提供某種平滑流量的功能。RateLimiter本意是 速率限制器,而它的2個實現都是平滑的!RateLimiter 有2個實現是 SmoothBursty和SmoothWarmingUp,兩個實現都是Smooth開頭,表明了其平滑的特性。所以,可以認為RateLimiter是平滑限流器!
SmoothBursty其實很簡單,也很好理解,它就是一個沒有上限的令牌桶, 因為沒有上限,所以允許突發的流量, 不管多少,都能夠接受!(當然,其實也是有上限的,是Integer.MAX_VALUE)。但是SmoothWarmingUp的理解難度陡然上升。看到一些文章說,SmoothWarmingUp 看起來是令牌桶, 也有說是漏銅,是嗎? 據我觀察,都不准確,如果非要選一個的話,那還是漏桶,因為它本質上是令牌桶上做了修改。SmoothWarmingUp的使用也不難,基本的理解也不難,難的是源碼。我從guava 16的版本的源碼看到如下的圖:
老實說,這個圖真難理解。后面我從最新的30.1-jre 版本看到這樣的圖:
其實現原理大同小異,但是顯然 最新的版本命名、設計更加合理, 所以我后面使用最新版本來做分析。最新版的限流器的名字也有一些變化:
WarmingUp -> SmoothWarmingUp
Bursty -> SmoothBursty
halfPermits -> thresholdPermits
可以發現命名更加合理。當然這個也不很重要。
關鍵字段
下面單獨分析 SmoothWarmingUp,即平滑預熱限流器。下面對源碼中限流器的關鍵字段或屬性做簡單介紹,父類提供的字段不難理解:
permitsPerSecond 穩定狀態下,每一秒產生多少個許可?
warmupPeriodMicros 預熱時間,單位是微秒
但下面的字段很難理解:
maxPermits 最大可以預存的許可。 這里我沒有直譯,而是使用了預存,當然,你可以理解為預取。 本意是從完全非飽和(冷卻)狀態到完全飽和(剛好完成預熱)狀態 的過程可以產生的許可。這個也很不好理解。但是非常關鍵。其實就是從冷卻到剛好完成預熱的時間內應該產生的許可。后面我們會發現,它是一個固定的值。為什么是固定? 其實是通過一系列假設,然后計算出來的。后面詳述。對於SmoothBursty來說,這個變量沒有意義。
storedPermits 實際預存的許可。這是一個動態變化的值,變化范圍就是上面那個圖,也就是0 - maxPermits ,你可以理解為預取。 本意是從限流器當前時間所處的狀態,到剛好完成預熱穩定運行的時間內應該產生的許可。如果當前限流器已經穩定運行,那么固定就是0;為什么要維護這樣一個值? 這個一開始真不好理解。 誰說限流器里面必須一個已存儲的許可字段? 它的實際意義是? 說不清它的實際意義,因為太抽象。 可以理解為預熱限流器內部維護的一個狀態,表明的冷熱程度。對於SmoothBursty來說,這個變量沒有意義。已存儲的許可? 其實有很大的誤導性,它並不是對之前未利用的許可的緩存或者存儲起來了,而是 表明一種未利用的狀態,或者說一種冷熱狀態!
stableIntervalMicros 穩定狀態下,每產生一個許可需要需要消耗的時間。其實說時間間隔 好像更准確,因為它只有在那種連續使用的狀態下才能保持穩定。它單位是微秒。stableIntervalMicros 其實就是速度的倒數。間隔越大,當然速度越低。反之亦反。我們把它簡稱為間隔。比如說速率即permitsPerSecond 是5個/s,那么穩定狀態產生一個許可的時間是 1/ permitsPerSecond = 0.2s
slope 斜率,也就是預熱的增速
thresholdPermits 許可門檻。限流器處於剛好完成預熱的時間點的時候,這個時間點上,速度剛剛好是stableIntervalMicros,如果閑置,那么立即又會開始冷卻,也就是速度變小, 它是一個臨界點。這個非常難理解。理解它表示一直臨界狀態非常關鍵。同時需要注意,為什么我們已經剛好完成了預熱,還不是0? 如果是0, 似乎更合理,但實際上,它就不是。
coldFactor 冷卻因子。用於計算從完全冷卻的狀態,啟動限流器,限流器的起始速度。我開始認為預熱的最開始的速度,應該是0, 這個是非常錯誤的理解,導致我后面花了很多時間去糾正。 其實不是的,一開始就有速度了,不過是比較慢(因為是冷卻狀態)
三個狀態&三個階段
我們要區分,穩定運行、完全預熱、預熱完成、剛好結束/完成預熱, 穩定運行或完全預熱是指storedPermits 為0 的時候,預熱完成、剛好結束/完成預熱是指 速率剛好達到穩定狀態的時候。 兩者還是不同的。 理解這點非常重要。
要想理解其原理,還有一點需要特別注意,平滑預熱限流器有一個冷卻的過程,這個過程和預熱是反向的。理解這個過程非常的關鍵。經過我反復的研究,發現其實這樣的過程:
冷卻狀態:storedPermits = maxPermits 的時候
預熱過程:間隔從coldFactor * stableIntervalMicros 逐步的、穩定的減少到 stableIntervalMicros,同時storedPermits 從maxPermits減少到 thresholdPermits 的過程 。耗時是warmupPeriodMicros。warmupPeriodMicros 是參數,是固定的,創建的時候就固定了,不能改,除非重建。
臨界狀態:storedPermits = thresholdPermits 的時候
准穩定階段:其實我一開始是把storedPermits 從maxPermits減少到0的過程, 當做了預熱,好像也沒啥大錯,但是這樣就不好理解源碼了! 而 storedPermits 從thresholdPermits 減少到0 的過程, 是一個穩定的過程,其實我們也可以給與一個命名: 准穩定過程, 我覺得這樣的命名是有意義的。。為什么冷卻過程的間隔保持不變為 stableIntervalMicros? 為什么不是從stableIntervalMicros 到 coldFactor * stableIntervalMicros ?一開始,我也認為是應該是一個逐步冷卻的過程,雖然這樣符合一個人的直觀感受,比如 冷卻肯定也是 非常熱,然后溫度慢慢下降吧, 比如開始的冷卻過程,烙鐵的冷卻等等。不好意思,RateLimiter 的冷卻就是戛然而止, 就是固定的時長的閑置,不減速的過程,然后戛然而止的速度降為0 (好像也不是0, 而是coldFactor * stableIntervalMicros ...),然后就認為是完全冷卻了下來。完全冷卻了下來,當然,就會有任何狀態的變化了,又相當於回到了 限流器剛創建的時候的樣子!
穩定狀態:storedPermits = 0的時候
冷卻階段:storedPermits 從0增加到maxPermits 的過程。因為間隔保持為 stableIntervalMicros ,耗時和冷卻時間和預熱時間完全一樣,同樣是warmupPeriodMicros。何以確定?兩個都是個人為設置的,是為了簡化計算。 其實完全可以是不同的。如果相同,所以我們可以計算冷卻的間隔:maxPermits / warmupPeriod,為什么?從而stableIntervalMicros = maxPermits / warmupPeriod,從而 maxPermits = warmupPeriod * stableIntervalMicros 。
非人類的設計啊! 難怪讓人很久理解不了!但 這一點思維必須改變過來。本質上說,WarmingUp還是令牌桶,也允許突發,但是每次啟動需要預熱,即warm/ramp up,然后閑下來之后,它會冷卻。冷卻的過程,好像也可以理解為漏桶.. 不過當然不是真正意義的漏桶。
理解了前面的各種說明,就不難理解官方的圖了。圖的橫軸是 存儲的許可。縱軸是限流器的速率狀態,准確說是限流器每產生一個許可的時間。 這兩個指標怎么會有如此密切的關聯? 而且是如此簡單的折線關系? 剛開始的時候,非常懵,后面才明白,其實也是蠻合理的,一切都是為了保證 預熱限流器的預熱特效,為了使這個特性完全實現, 也是想盡辦法。
首先,坐標系原點到底是什么東西? 確實也不好理解。按照上面的分析,原點其實就是穩定運行時候的限流器的狀態。但對於預熱過程,它其實沒有意義。對於冷卻過程,它是冷卻過程的起點! 理解這點非常重要。 雖然是兩個相反的過程,但是也完全有機的整合到了一個坐標系。我想如果分開為下面的兩個圖, 是不是更加好理解呢?
實際中,兩個過程除了有一個共同的起點、終點之后,其實沒其他關系了。所以說,實際關系不大,但我們可以做一些假設,以簡化問題。
坐標系的面積是什么?是時間! 一般來說,時間只是一個坐標,但是這里做了坐標的變換,這里面積竟然是時間! 千萬要注意!
計算和求解
隨便在橫軸的一個點(不能超出0 - max),那么限流器所處的狀態就是半冷半熱狀態!如果我們畫一條豎線,那么左邊的面積是到剛好完成預熱 並且到穩定狀態的時間。而右邊的就是它到完全冷卻需要的時間。注意到 冷卻的時間是 一個矩形。而預熱+准穩定的時間是 一個 不規則四邊形! 為什么不規則啊!其實官方的圖有很大的誤導性,而且剛好 冷卻速度和穩定的速度是一樣的,所以重疊為一個不規則四邊形。如果分開兩個圖, 會更加好理解!對於下圖任意一點,其實都是處於半冷半熱的狀態,如果繼續冷卻(即閑置),那么往右走,最多到maxPermits位置,如果重新使用(即調用acquire方法),那么往左走,最多走到原點。往左走,獲取N個permit,那么可能是下圖所示,可以累計計算綠色和紅色部分的面積 作為時間!
再次說明,storedPermits 是目前為止,過去的未利用的資源; 冷卻之后再次使用,是否還需要預熱?是應該更慢還是更快? 這個其實沒有唯一答案,還是得看具體場景; 啟動時候的初始預熱和冷卻之后再次使用,其實還是不同的,所以; 其實實際的工業應用中,情況可能更加復雜。那就可能不是簡單一條折線能夠描述的了! an unused RateLimiter 實際表示限流器的下一個可以實際獲取的許可 是之前的時間。 acquire(100) 難道需要等100s? 顯然非常不合理。實際的做法都是,直接開始,然后把后續的請求延遲,延遲到acquire(100) 實際結束的時間點。 acquire(3) is equivalent to { acquire(1); acquire(1); acquire(1); }, or { acquire(2); acquire(1); }, etc, since the integral of the function in [7.0, 10.0] is equivalent to the sum of the integrals of [7.0, 8.0], [8.0, 9.0], [9.0, 10.0] (and so on), 問題是,maxPermits的含義是? 其實maxPermits 跟預熱沒關系,它表明的是 完全穩定到完全冷卻之間可未被利用的 許可。是緩存起來的許可。 因為 冷卻時間是固定的,冷卻速度也是確定的, 所以 maxPermits 就這樣被計算出來了!冷卻時間、 冷卻速度 是兩個假設,完全是人為的假設; thresholdPermits 是怎么計算的? 首先maxPermits 已經有了,然后冷卻的面積因為涉及thresholdPermits,不可以直接計算,我們需要通過列方程計算:
假設
1 冷卻速度固定,間隔為stableIntervalMicros
2 冷卻時間 = 預熱時間
那么有:
warmupPeriodMicros = coolDownPeriodMicros
warmupPeriodMicros = (maxPermits - thresholdPermits) / (stableIntervalMicros + coldFactor * stableIntervalMicros) / 2
coolDownPeriodMicros = stableIntervalMicros * maxPermits
其中 warmupPeriodMicros、coldFactor 是參數,coldFactor固定是3;stableIntervalMicros 也可以由參數直接計算。故這是一個三元一次方程, 變量為coolDownPeriodMicros maxPermits thresholdPermits。 如果2個假設不滿足,那么無法求解!
計算可得:
thresholdPermits = maxPermits - 2 * maxPermits / ( 1 + coldFactor)
令coldFactor = 3, 那么 thresholdPermits = maxPermits / 2 。這個也是默認設置。
令coldFactor = 1, 那么 thresholdPermits = 0; 也就是說, 如果預熱也是平直的過程,那么。。
令coldFactor = 5, 那么 thresholdPermits = 2 * maxPermits / 3; 以此類推。。
總之,不管coldFactor 是多少, 完全穩定到完全冷卻的圖形中,消耗 maxPermits 是相同的, 它們右邊的圖形是部分重疊的。 如果maxPermits 不相同? 那么問題不好求解。
很累的總結
為什么從 冷卻、半冷卻狀態回到 完全預熱狀態 需要走一條折線? 這個是最難理解的?為什么 預熱結束后 還需要走一段橫線。從完全冷卻 預熱到完全穩定, 需要消耗maxPermits , 花費時間是 warmupPeriodMicros + stableIntervalMicros * thresholdPermits; 而從完全穩定冷卻到 完全冷卻,需要的時間是warmupPeriodMicros , 兩者並不一樣。為什么不一樣? 為什么不是一個逆過程? 感覺還是因為預熱。 就是說 預熱需要時間,速度比較慢,冷卻就快一些!如果是這樣, warmupPeriod = coolDownPeriod 還能夠成立嗎? 看你怎么理解 warmupPeriod , warmupPeriod其實是加速的過程,但是速度穩定之后, 並不是立即預熱完畢。 比較拗口啊。
反反復復說了這么多,自己都覺得非常啰嗦了。總之 thresholdPermits 所處時間點,是個臨界點。 理解上面各個關鍵字段和各種狀態轉變了之后,你就不會有那么多為什么了,然后源碼也就不難理解了!