關(guān)于Sentinel中冷啟動限流原理WarmUpController
冷啟動
所謂冷啟動,或預(yù)熱是指,系統(tǒng)長時(shí)間處理低水平請求狀態(tài),當(dāng)大量請求突然到來時(shí),并非所有請求都放行,而是慢慢的增加請求,目的時(shí)防止大量請求沖垮應(yīng)用,達(dá)到保護(hù)應(yīng)用的目的。
Sentinel中冷啟動是采用令牌桶算法實(shí)現(xiàn)。
令牌桶算法圖例如下:
預(yù)熱模型
Sentinel中的令牌桶算法,是參照Google Guava中的RateLimiter,在學(xué)習(xí)Sentinel中預(yù)熱算法之前,先了解下整個(gè)預(yù)熱模型,如下圖:
Guava中預(yù)熱是通過控制令牌的生成時(shí)間,而Sentinel中實(shí)現(xiàn)不同:
- 不控制每個(gè)請求通過的時(shí)間間隔,而是控制每秒通過的請求數(shù)。
- 在Guava中,冷卻因子coldFactor固定為3,上圖中②是①的兩倍
- Sentinel增加冷卻因子coldFactor的作用,在Sentinel模型中,②是①的(coldFactor-1)倍,coldFactor默認(rèn)為3,可以通過csp.sentinel.flow.cold.factor參數(shù)修改
原理分析
Sentinel中冷啟動對應(yīng)的FlowRule配置為RuleConstant.CONTROL_BEHAVIOR_WARM_UP,對應(yīng)的Controller為WarmUpController,首先了解其中的屬性和構(gòu)造方法:
count
:FlowRule中設(shè)定的閾值warmUpPeriodSec
:系統(tǒng)預(yù)熱時(shí)間,代表上圖中的②coldFactor
:冷卻因子,默認(rèn)為3,表示倍數(shù),即系統(tǒng)最"冷"時(shí)(令牌桶飽和時(shí)),令牌生成時(shí)間間隔是正常情況下的多少倍warningToken
:預(yù)警值,表示進(jìn)入預(yù)熱或預(yù)熱完畢maxToken
:最大可用token值,計(jì)算公式:warningToken+(2*時(shí)間*閾值)/(1+因子),默認(rèn)情況下為warningToken的2倍slope
:斜度,(coldFactor-1)/count/(maxToken-warningToken),用于計(jì)算token生成的時(shí)間間隔,進(jìn)而計(jì)算當(dāng)前token生成速度,最終比較token生成速度與消費(fèi)速度,決定是否限流storedTokens
:姑且可以理解為令牌桶中令牌的數(shù)量
public class WarmUpController implements TrafficShapingController { // FlowRule中設(shè)置的閾值 protected double count; // 冷卻因子,默認(rèn)為3,通過SentinelConfig加載,可以修改 private int coldFactor; // 預(yù)警token數(shù)量 protected int warningToken = 0; // 最大token數(shù)量 private int maxToken; // 斜率,用于計(jì)算當(dāng)前生成token的時(shí)間間隔,即生成速率 protected double slope; // 令牌桶中剩余令牌數(shù) protected AtomicLong storedTokens = new AtomicLong(0); // 最后一次添加令牌的時(shí)間戳 protected AtomicLong lastFilledTime = new AtomicLong(0); public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) { construct(count, warmUpPeriodInSec, coldFactor); } public WarmUpController(double count, int warmUpPeriodInSec) { construct(count, warmUpPeriodInSec, 3); } private void construct(double count, int warmUpPeriodInSec, int coldFactor) { if (coldFactor <= 1) { throw new IllegalArgumentException("Cold factor should be larger than 1"); } this.count = count; // 默認(rèn)為3 this.coldFactor = coldFactor; // thresholdPermits = 0.5 * warmupPeriod / stableInterval. // warningToken = 100; // 計(jì)算預(yù)警token數(shù)量 // 例如 count=5,warmUpPeriodInSec=10,coldFactor=3,則waringToken=5*10/2=25 warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1); // / maxPermits = thresholdPermits + 2 * warmupPeriod / (stableInterval + coldInterval) // maxToken = 200 // 最大token數(shù)量=25+2*10*5/4=50 maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor)); // slope // slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits- thresholdPermits); // 傾斜度=(3-1)/5/(50-25) = 0.016 slope = (coldFactor - 1.0) / count / (maxToken - warningToken); } }
舉例說明:
FlowRule設(shè)定閾值count=5,即1s內(nèi)QPS閾值為5,設(shè)置的預(yù)熱時(shí)間默認(rèn)為10s,即warmUpPeriodSec=10,冷卻因子coldFactor默認(rèn)為3,即count = 5,coldFactor=3,warmUpPeriodSec=10,則
stableInterval=1/count=200ms,coldInterval=coldFactor*stableInterval=600ms warningToken=warmUpPeriodSec/(coldFactor-1)/stableInterval=(warmUpPeriodSec*count)/(coldFactor-1)=25 maxToken=2warmUpPeriodSec/(stableInterval+coldInterval)+warningToken=warningToken+2warmUpPeriodSeccount/(coldFactor+1)=50 slope=(coldInterval-stableInterval)/(maxToken-warningToken)=(coldFactor-1)/count/(maxToken-warningToken)=0.016
接下來學(xué)習(xí),WarmUpController是如何進(jìn)行限流的,進(jìn)入canPass()方法:
public boolean canPass(Node node, int acquireCount, boolean prioritized) { // 獲取當(dāng)前1s的QPS long passQps = (long) node.passQps(); // 獲取上一窗口通過的qps long previousQps = (long) node.previousPassQps(); // 生成和滑落token syncToken(previousQps); // 如果進(jìn)入了警戒線,開始調(diào)整他的qps long restToken = storedTokens.get(); // 如果令牌桶中的token數(shù)量大于警戒值,說明還未預(yù)熱結(jié)束,需要判斷token的生成速度和消費(fèi)速度 if (restToken >= warningToken) { long aboveToken = restToken - warningToken; // 消耗的速度要比warning快,但是要比慢 // y軸,當(dāng)前token生成時(shí)間 current interval = restToken*slope+stableInterval // 計(jì)算此時(shí)1s內(nèi)能夠生成token的數(shù)量 double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); // 判斷token消費(fèi)速度是否小于生成速度,如果是則正常請求,否則限流 if (passQps + acquireCount <= warningQps) { return true; } } else { // 預(yù)熱結(jié)束,直接判斷是否超過設(shè)置的閾值 if (passQps + acquireCount <= count) { return true; } } return false; }
canPass()方法分為3個(gè)階段:
syncToken():負(fù)責(zé)令牌的生產(chǎn)和滑落
判斷令牌桶中剩余令牌數(shù)
- 如果剩余令牌數(shù)大于警戒值,說明處于預(yù)熱階段,需要比較令牌的生產(chǎn)速率與令牌的消耗速率。若消耗速率大,則限流;否則請求正常通行
仍然以count=5進(jìn)行舉例,警戒線warningToken=25,maxToken=50
假設(shè)令牌桶中剩余令牌數(shù)storedTokens=30,即在預(yù)熱范圍內(nèi),此時(shí)restToken=30,slope=0.016,則aboveToken=30-25=5
由斜率slope推導(dǎo)當(dāng)前token生成時(shí)間間隔:(restToken-warningToken)*slope+stableInterval=5*0.016+1/5=0.28,即280ms生成一個(gè)token
此時(shí)1s內(nèi)生成token的數(shù)量=1/0.28≈4,即1s內(nèi)生成4個(gè)token
假設(shè)當(dāng)前窗口通過的請求數(shù)量passQps=4,acquiredCount=1,此時(shí)passQps+acquiredCount=5>4,即令牌消耗速度大于生產(chǎn)速度,則限流
- 如果剩余令牌數(shù)小于警戒值,說明系統(tǒng)已經(jīng)處于高水位,請求穩(wěn)定,則直接判斷QPS與閾值,超過閾值則限流
接下來分析Sentinel是如何生產(chǎn)及滑落token的,進(jìn)入到syncToken()方法:
獲取當(dāng)前時(shí)間秒數(shù)currentTime,與lastFilledTime進(jìn)行比較,之所以取秒數(shù),是因?yàn)闀r(shí)間窗口的設(shè)定為1s,若兩個(gè)時(shí)間相等,說明還處于同一秒內(nèi),不進(jìn)行token填充和滑落,避免重復(fù)問題
令牌桶中添加token
- 當(dāng)流量極大,令牌桶中剩余token遠(yuǎn)低于預(yù)警值時(shí),添加token
- 處于預(yù)熱節(jié)點(diǎn),單令牌的消耗速度小于系統(tǒng)最冷時(shí)令牌的生成速度,則添加令牌
通過CAS操作,修改storedToken,并進(jìn)行令牌扣減
protected void syncToken(long passQps) { long currentTime = TimeUtil.currentTimeMillis(); // 獲取整秒數(shù) currentTime = currentTime - currentTime % 1000; // 上一次的操作時(shí)間 long oldLastFillTime = lastFilledTime.get(); // 判斷成立,如果小于,說明可能出現(xiàn)了時(shí)鐘回?fù)? // 如果等于,說明當(dāng)前請求都處于同一秒內(nèi),則不進(jìn)行token添加和滑落操作,避免的重復(fù)扣減 // 時(shí)間窗口的跨度為1s if (currentTime <= oldLastFillTime) { return; } // token數(shù)量 long oldValue = storedTokens.get(); long newValue = coolDownTokens(currentTime, passQps); // 重置token數(shù)量 if (storedTokens.compareAndSet(oldValue, newValue)) { // token滑落,即token消費(fèi) // 減去上一個(gè)時(shí)間窗口的通過請求數(shù) long currentValue = storedTokens.addAndGet(0 - passQps); if (currentValue < 0) { storedTokens.set(0L); } // 設(shè)置最后添加令牌時(shí)間 lastFilledTime.set(currentTime); } } private long coolDownTokens(long currentTime, long passQps) { long oldValue = storedTokens.get(); long newValue = oldValue; // 添加令牌的判斷前提條件: // 當(dāng)令牌的消耗程度遠(yuǎn)遠(yuǎn)低于警戒線的時(shí)候 if (oldValue < warningToken) { // 計(jì)算過去一段時(shí)間內(nèi),可以通過的QPS總量 // 初始加載時(shí),令牌數(shù)量達(dá)到maxToken newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); } else if (oldValue > warningToken) { // 處于預(yù)熱過程,且消費(fèi)速度低于冷卻速度,則補(bǔ)充令牌 if (passQps < (int)count / coldFactor) { newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); } } // 當(dāng)令牌桶滿了之后,拋棄多余的令牌 return Math.min(newValue, maxToken); }
總結(jié)
Sentinel采用令牌桶算法實(shí)現(xiàn)預(yù)熱限流
系統(tǒng)流量突增,令牌消耗從maxPermits(令牌桶容量)到thresholdPermits(警戒線)所需要的時(shí)間,是從警戒線到0的(coldFactor-1)倍,并非其他博客中的2倍。另外,關(guān)于預(yù)熱模型中②和①的關(guān)系,是通過結(jié)果反推而來,并沒有找到模型定義的官方文檔。
Sentinel限流是針對某時(shí)刻令牌的生成與消耗速度
Sentinel通過比較整秒數(shù),來判斷是否需要進(jìn)行令牌扣減,并通過CAS操作,保證同一時(shí)刻只能由1個(gè)線程成功操作,從而避免多次扣減passQps導(dǎo)致限流失效的問題
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合SSO(single sign on)單點(diǎn)登錄
這篇文章主要介紹了SpringBoot整合SSO(single sign on)單點(diǎn)登錄,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06詳解Spring整合mybatis--Spring中的事務(wù)管理(xml形式)
這篇文章主要介紹了Spring整合mybatis--Spring中的事務(wù)管理(xml形式),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-11-11Java二分法查找_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java二分法查找的相關(guān)資料,需要的朋友可以參考下2017-04-04java開源區(qū)塊鏈初始化創(chuàng)世區(qū)塊jdchain服務(wù)搭建
這篇文章主要介紹了java開源區(qū)塊鏈初始化創(chuàng)世區(qū)塊jdchain的服務(wù)搭建步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02關(guān)于Spring?Cloud的熔斷器監(jiān)控問題
Turbine是一個(gè)聚合Hystrix監(jiān)控?cái)?shù)據(jù)的工具,它可將所有相關(guān)/hystrix.stream端點(diǎn)的數(shù)據(jù)聚合到一個(gè)組合的/turbine.stream中,從而讓集群的監(jiān)控更加方便,接下來通過本文給大家介紹Spring?Cloud的熔斷器監(jiān)控,感興趣的朋友一起看看吧2022-01-01Java結(jié)合redis實(shí)現(xiàn)接口防重復(fù)提交
本文主要介紹了Java結(jié)合redis實(shí)現(xiàn)接口防重復(fù)提交,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09JDBC簡介_動力節(jié)點(diǎn)Java學(xué)院整理
什么是JDBC?這篇文章就為大家詳細(xì)介紹了Java語言中用來規(guī)范客戶端程序如何來訪問數(shù)據(jù)庫的應(yīng)用程序接口,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07WebSocket實(shí)現(xiàn)數(shù)據(jù)庫更新時(shí)前端頁面刷新
這篇文章主要為大家詳細(xì)介紹了WebSocket實(shí)現(xiàn)數(shù)據(jù)庫更新時(shí)前端頁面刷新,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04