關于Sentinel中冷啟動限流原理WarmUpController
冷啟動
所謂冷啟動,或預熱是指,系統(tǒng)長時間處理低水平請求狀態(tài),當大量請求突然到來時,并非所有請求都放行,而是慢慢的增加請求,目的時防止大量請求沖垮應用,達到保護應用的目的。
Sentinel中冷啟動是采用令牌桶算法實現(xiàn)。
令牌桶算法圖例如下:

預熱模型
Sentinel中的令牌桶算法,是參照Google Guava中的RateLimiter,在學習Sentinel中預熱算法之前,先了解下整個預熱模型,如下圖:

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

