一、引子
前面介紹了SystemSlot(系統(tǒng)規(guī)則檢查)和AuthoritySlot(授權(quán)規(guī)則檢查)狡耻,下面接著分析FlowSlot墩剖。
FlowSlot 會根據(jù)預(yù)設(shè)的規(guī)則猴凹,結(jié)合前面 NodeSelectorSlot、ClusterNodeBuilderSlot岭皂、StatistcSlot 統(tǒng)計出來的實時信息進行流量控制郊霎。
限流的直接表現(xiàn)是在執(zhí)行 Entry nodeA = SphU.entry(資源名字) 的時候拋出 FlowException 異常。FlowException 是 BlockException 的子類蒲障,您可以捕捉 BlockException 來自定義被限流之后的處理邏輯歹篓。
同一個資源可以對應(yīng)多條限流規(guī)則。FlowSlot 會對該資源的所有限流規(guī)則依次遍歷揉阎,直到有規(guī)則觸發(fā)限流或者所有規(guī)則遍歷完畢庄撮。
一條限流規(guī)則主要由下面幾個因素組成,我們可以組合這些元素來實現(xiàn)不同的限流效果:
resource:資源名毙籽,即限流規(guī)則的作用對象
count: 限流閾值
grade: 限流閾值類型洞斯,QPS 或線程數(shù)
strategy: 根據(jù)調(diào)用關(guān)系選擇策略:直接、關(guān)聯(lián)坑赡、鏈路
clusterMode:是否集群模式
controlBehavior:流控效果:快速失敗烙如、WarmUP、排隊等候毅否、WarmUP+排隊等候
refResource:關(guān)聯(lián)資源
在dashborad中亚铁,可以設(shè)置資源的流控規(guī)則:如圖
二 、基于QPS/并發(fā)數(shù)的流量控制
流量控制主要有兩種統(tǒng)計類型螟加,一種是統(tǒng)計線程數(shù)徘溢,另外一種則是統(tǒng)計 QPS。類型由 FlowRule.grade 字段來定義捆探。其中然爆,0 代表根據(jù)線程并發(fā)數(shù)量來限流,1 代表根據(jù) QPS 來進行流量控制黍图。其中線程數(shù)曾雕、QPS 值,都是由 StatisticSlot 實時統(tǒng)計獲取的助被。
可以通過下面的命令查看實時統(tǒng)計信息:
curl http://localhost:8719/cnode?id=resourceName
輸出內(nèi)容格式如下:
idx id thread pass blocked success total Rt 1m-pass 1m-block 1m-all exeption
2 abc647 0 46 0 46 46 1 2763 0 2763 0
其中:
thread: 代表當前處理該資源的線程數(shù)剖张;
pass: 代表一秒內(nèi)到來到的請求;
blocked: 代表一秒內(nèi)被流量控制的請求數(shù)量恰起;
success: 代表一秒內(nèi)成功處理完的請求修械;
total: 代表到一秒內(nèi)到來的請求以及被阻止的請求總和;
RT: 代表一秒內(nèi)該資源的平均響應(yīng)時間检盼;
1m-pass: 則是一分鐘內(nèi)到來的請求;
1m-block: 則是一分鐘內(nèi)被阻止的請求翘单;
1m-all: 則是一分鐘內(nèi)到來的請求和被阻止的請求的總和吨枉;
exception: 則是一秒內(nèi)業(yè)務(wù)本身異常的總和蹦渣。
2.1并發(fā)線程數(shù)流量控制
線程數(shù)限流用于保護業(yè)務(wù)線程數(shù)不被耗盡。例如貌亭,當應(yīng)用所依賴的下游應(yīng)用由于某種原因?qū)е路?wù)不穩(wěn)定柬唯、響應(yīng)延遲增加,對于調(diào)用者來說圃庭,意味著吞吐量下降和更多的線程數(shù)占用锄奢,極端情況下甚至導(dǎo)致線程池耗盡。
為應(yīng)對高線程占用的情況剧腻,業(yè)內(nèi)有使用隔離的方案拘央,比如通過不同業(yè)務(wù)邏輯使用不同線程池來隔離業(yè)務(wù)自身之間的資源爭搶(線程池隔離),或者使用信號量來控制同時請求的個數(shù)(信號量隔離)书在。
- 線程池隔離:分配一個線程池來處理這些資源灰伟。當沒有更多的空閑線程池中,請求被拒絕而不影響其他資源。使用線程池的好處是,它可以當超時后優(yōu)雅地隔開儒旬,但它也給我們帶來線程上下文切換和額外的成本栏账。如果傳入的請求已經(jīng)在獨立的線程,例如:servelet請求,它將會幾乎兩倍于如果使用線程池線程計數(shù)。
- 信號量隔離:在這個資源中使用信號量來控制線程的并發(fā)數(shù)栈源。
這種隔離方案雖然能夠控制線程數(shù)量挡爵,但無法控制請求排隊時間。當請求過多時排隊也是無益的甚垦,直接拒絕能夠迅速降低系統(tǒng)壓力茶鹃。Sentinel線程數(shù)限流不負責創(chuàng)建和管理線程池,而是簡單統(tǒng)計當前請求上下文的線程個數(shù)制轰,如果超出閾值前计,新的請求會被立即拒絕。
2.2QPS流量控制
當 QPS 超過某個閾值的時候垃杖,則采取措施進行流量控制男杈。流量控制的手段包括下面 4 種,對應(yīng) FlowRule 中的 controlBehavior 字段:
- 直接拒絕(
RuleConstant.CONTROL_BEHAVIOR_DEFAULT
)方式调俘。該方式是默認的流量控制方式伶棒,當QPS超過任意規(guī)則的閾值后,新的請求就會被立即拒絕彩库,拒絕方式為拋出FlowException肤无。這種方式適用于對系統(tǒng)處理能力確切已知的情況下,比如通過壓測確定了系統(tǒng)的準確水位時骇钦。 - 冷啟動(
RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式宛渐。該方式主要用于系統(tǒng)長期處于低水位的情況下,當流量突然增加時,直接把系統(tǒng)拉升到高水位可能瞬間把系統(tǒng)壓垮窥翩。通過"冷啟動"业岁,讓通過的流量緩慢增加,在一定時間內(nèi)逐漸增加到閾值上限寇蚊,給冷系統(tǒng)一個預(yù)熱的時間笔时,避免冷系統(tǒng)被壓垮的情況。
通常冷啟動的過程系統(tǒng)允許通過的 QPS 曲線如下圖所示:
- 勻速器(
RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式仗岸。這種方式嚴格控制了請求通過的間隔時間允耿,也即是讓請求以均勻的速度通過,對應(yīng)的是漏桶算法扒怖。
該方式的作用如下圖所示:
勻速器
這種方式主要用于處理間隔性突發(fā)的流量较锡,例如消息隊列。想象一下這樣的場景姚垃,在某一秒有大量的請求到來念链,而接下來的幾秒則處于空閑狀態(tài),我們希望系統(tǒng)能夠在接下來的空閑期間逐漸處理這些請求积糯,而不是在第一秒直接拒絕多余的請求掂墓。
- 冷啟動+勻速器(
RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER
)方式。除了讓流量緩慢增加看成,還控制的了請求的間隔時間君编,讓請求已均勻速度通過。這種策略是1.4.0版本新增的川慌。
三吃嘿、基于調(diào)用關(guān)系的流量控制
調(diào)用關(guān)系包括調(diào)用方、被調(diào)用方梦重;方法又可能會調(diào)用其它方法兑燥,形成一個調(diào)用鏈路的層次關(guān)系。Sentinel 通過 NodeSelectorSlot 建立不同資源間的調(diào)用的關(guān)系琴拧,通過ClusterBuilderSlot設(shè)置每個資源源節(jié)點降瞳,并且通過 StatisticSlot 記錄每個資源的實時統(tǒng)計信息。
3.1 根據(jù)調(diào)用方限流
RuleConstant.STRATEGY_DIRECT = 0
ContextUtil.enter(resourceName, origin)
方法中的 origin 參數(shù)標明了調(diào)用方身份蚓胸。這些信息會在 StatisticSlot 中被統(tǒng)計挣饥。
限流規(guī)則中的 limitApp 字段用于根據(jù)調(diào)用方進行流量控制。該字段的值有以下三種選項沛膳,分別對應(yīng)不同的場景:
- default:表示不區(qū)分調(diào)用者扔枫,來自任何調(diào)用者的請求都將進行限流統(tǒng)計。如果這個資源名的調(diào)用總和超過了這條規(guī)則定義的閾值锹安,則觸發(fā)限流短荐。
- {some_origin_name}:表示針對特定的調(diào)用者倚舀,只有來自這個調(diào)用者的請求才會進行流量控制。例如 NodeA 配置了一條針對調(diào)用者caller1的規(guī)則搓侄,那么當且僅當來自 caller1 對 NodeA 的請求才會觸發(fā)流量控制瞄桨。
- other:表示針對除 {some_origin_name} 以外的其余調(diào)用方的流量進行流量控制话速。例如讶踪,資源NodeA配置了一條針對調(diào)用者 caller1 的限流規(guī)則,同時又配置了一條調(diào)用者為 other 的規(guī)則泊交,那么任意來自非 caller1 對 NodeA 的調(diào)用乳讥,都不能超過 other 這條規(guī)則定義的閾值。
同一個資源名可以配置多條規(guī)則廓俭,規(guī)則的生效順序為:{some_origin_name} > other > default
3.2 具有關(guān)系的資源流量控制:關(guān)聯(lián)流量控制
RuleConstant.STRATEGY_RELATE = 1
當兩個資源之間具有資源爭搶或者依賴關(guān)系的時候云石,這兩個資源便具有了關(guān)聯(lián)。比如對數(shù)據(jù)庫同一個字段的讀操作和寫操作存在爭搶研乒,讀的速度過高會影響寫得速度汹忠,寫的速度過高會影響讀的速度。如果放任讀寫操作爭搶資源雹熬,則爭搶本身帶來的開銷會降低整體的吞吐量宽菜。
可使用關(guān)聯(lián)限流來避免具有關(guān)聯(lián)關(guān)系的資源之間過度的爭搶,舉例來說竿报,read_db 和 write_db 這兩個資源分別代表數(shù)據(jù)庫讀寫铅乡,我們可以給 read_db 設(shè)置限流規(guī)則來達到寫優(yōu)先的目的:設(shè)置 FlowRule.strategy 為 RuleConstant.STRATEGY_RELATE 同時設(shè)置 FlowRule.refResource 為 write_db。這樣當寫庫操作過于頻繁時烈菌,讀數(shù)據(jù)的請求會被限流阵幸。
3.3 根據(jù)調(diào)用鏈路入口限流:鏈路限流
RuleConstant.STRATEGY_CHAIN = 2
NodeSelectorSlot 中記錄了資源之間的調(diào)用鏈路,這些資源通過調(diào)用關(guān)系芽世,相互之間構(gòu)成一棵調(diào)用樹挚赊。這棵樹的根節(jié)點是一個名字為 machine-root 的虛擬節(jié)點,調(diào)用鏈的入口都是這個虛節(jié)點的子節(jié)點济瓢。
一棵典型的調(diào)用樹如下圖所示:
machine-root
/ \
/ \
Entrance1 Entrance2
/ \
/ \
DefaultNode(nodeA) DefaultNode(nodeA)
上圖中來自入口 Entrance1 和 Entrance2 的請求都調(diào)用到了資源 NodeA荠割,Sentinel 允許只根據(jù)某個入口的統(tǒng)計信息對資源限流。
比如我們可以設(shè)置 FlowRule.strategy 為 RuleConstant.STRATEGY_CHAIN葬荷,同時設(shè)置 FlowRule.refResource 為 Entrance1 來表示只有從入口 Entrance1 的調(diào)用才會記錄到 NodeA 的限流統(tǒng)計當中涨共,而對來自 Entrance2 的調(diào)用漠不關(guān)心。
調(diào)用鏈的入口是通過 API 方法 ContextUtil.enter(name) 定義的宠漩。
四举反、源碼分析
4.1 FlowSlot
首先看FlowSlot入口類:
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
checkFlow(resourceWrapper, context, node, count, prioritized);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
// Flow rule map cannot be null.
Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
List<FlowRule> rules = flowRules.get(resource.getName());
if (rules != null) {
for (FlowRule rule : rules) {
if (!canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp());
}
}
}
}
boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int count, boolean prioritized) {
return FlowRuleChecker.passCheck(rule, context, node, count, prioritized);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
fireExit(context, resourceWrapper, count, args);
}
大致內(nèi)容:
1.通過FlowRuleManage獲取所有的限流規(guī)則
2.獲取該資源對應(yīng)的限流,然后循環(huán)通過canPassCheck方法判斷扒吁,若返回false則說明被限流了火鼻。
4.2 FlowRuleChecker類
static boolean passCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
String limitApp = rule.getLimitApp();
if (limitApp == null) {
return true;
}
if (rule.isClusterMode()) {
return passClusterCheck(rule, context, node, acquireCount, prioritized);
}
return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
if (selectedNode == null) {
return true;
}
return rule.getRater().canPass(selectedNode, acquireCount);
}
1室囊、通過selectNodeByRequesterAndStrategy方法選擇被限流的節(jié)點。
2魁索、獲取的rule的Controller調(diào)用具體的限流規(guī)則融撞。
static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
// 獲取限流的limitApp,限流策略(startegy)粗蔚,上線的origin
String limitApp = rule.getLimitApp();
int strategy = rule.getStrategy();
String origin = context.getOrigin();
//如果limitApp等于origin并且origin不是default和other尝偎;
if (limitApp.equals(origin) && filterOrigin(origin)) {
//如果策略是STRATEGY_DIRECT(調(diào)用方限流)
if (strategy == RuleConstant.STRATEGY_DIRECT) {
// Matches limit origin, return origin statistic node.
return context.getOriginNode();
}
return selectReferenceNode(rule, context, node);
} else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
if (strategy == RuleConstant.STRATEGY_DIRECT) {
// Return the cluster node.
return node.getClusterNode();
}
return selectReferenceNode(rule, context, node);
} else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
&& FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
if (strategy == RuleConstant.STRATEGY_DIRECT) {
return context.getOriginNode();
}
return selectReferenceNode(rule, context, node);
}
return null;
}
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
String refResource = rule.getRefResource();
int strategy = rule.getStrategy();
if (StringUtil.isEmpty(refResource)) {
return null;
}
if (strategy == RuleConstant.STRATEGY_RELATE) {
return ClusterBuilderSlot.getClusterNode(refResource);
}
if (strategy == RuleConstant.STRATEGY_CHAIN) {
if (!refResource.equals(context.getName())) {
return null;
}
return node;
}
// No node.
return null;
}
1.獲取限流的limitApp,限流策略(startegy)鹏控,上線的origin致扯。
2.如果limitApp等于origin并且origin不是default和other:
如果策略是STRATEGY_DIRECT(調(diào)用方限流),則限流節(jié)點是
originNode
当辐;若是限流策略是STRATEGY_RELATE(關(guān)聯(lián)限流)抖僵,則限流節(jié)點是refResource的clusterNode;若是限流策略是STRATEGY_CHAIN(鏈路限流)缘揪,并且refResource等于contextName耍群,則限流節(jié)點就是node
。
3.如果limitApp等于default:
如果策略是STRATEGY_DIRECT(調(diào)用方限流)找筝,則限流節(jié)點是
clusterNode
蹈垢;若是限流策略是STRATEGY_RELATE(關(guān)聯(lián)限流),則限流節(jié)點是refResource的clusterNode呻征;若是限流策略是STRATEGY_CHAIN(鏈路限流)耘婚,并且refResource等于contextName,則限流節(jié)點就是node
陆赋。
4.如果limitApp等于other并且該資源的其他限流limitApp不與origin相同:
如果策略是STRATEGY_DIRECT(調(diào)用方限流)沐祷,則限流節(jié)點是
originNode
;若是限流策略是STRATEGY_RELATE(關(guān)聯(lián)限流)攒岛,則限流節(jié)點是refResource的clusterNode赖临;若是限流策略是STRATEGY_CHAIN(鏈路限流),并且refResource等于contextName灾锯,則限流節(jié)點就是node
兢榨。
4.3 流控規(guī)則
由rule.getRater()獲取具體的流控規(guī)則,目前有四種流控規(guī)則顺饮;直接失敗拆讯、WarmUP芯杀、排隊等候、WarmUP+排隊等候。
流控規(guī)則是在FlowRuleUtil類中設(shè)置的嘿架,根據(jù)具體的ControlBefavior進行設(shè)置甸鸟,如下代碼:
private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) {
if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
switch (rule.getControlBehavior()) {
case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(),
ColdFactorProperty.coldFactor);
case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(),
rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor);
case RuleConstant.CONTROL_BEHAVIOR_DEFAULT:
default:
// Default mode or unknown mode: default traffic shaping controller (fast-reject).
}
}
return new DefaultController(rule.getCount(), rule.getGrade());
}
可以發(fā)現(xiàn)對應(yīng)關(guān)系如下:
流控類型 | Controller |
---|---|
快速失敗 | DefaultController |
WarmUp | WarmUpController |
勻速排隊 | RateLimiterController |
WarmUp + 勻速排隊 | WarmUpRateLimiterController |
快速失敗
1.通過avgUsedTokens
方法先獲取當前的請求線程數(shù)或者qps第队,然后加上當前請求的個數(shù)acquireCount
,如果大于count則說明超過了限流控制的閾值励稳,則返回false。
Warp Up
Sentinel的WarmUp是基于Guava的算法囱井,但是不像Guava的場景驹尼,這是基于一個漏桶,主要使用基于時間間隔庞呕,
Sentinel更專注于控制計數(shù)每秒的請求而沒有計算它的間隔新翎。
Sentinel的WarmUp算法實現(xiàn)基于基于Guava的算法。然而,Guava的實現(xiàn)重點調(diào)整請求的時間間隔,換句話說,一個漏水的水桶千扶。哨兵更多關(guān)注控制計數(shù)每秒的請求沒有計算它的間隔,它更像是一個“令牌桶料祠。
剩下的令牌桶是用來測量系統(tǒng)效用。假設(shè)一個系統(tǒng)可以處理b每秒的請求澎羞。每秒鐘b標記將被添加到桶,直到桶滿了。系統(tǒng)處理一個請求時,它需要一個令牌桶敛苇。剩有令牌桶,降低系統(tǒng)的利用率;令牌桶中的令牌時超過一定閾值,我們稱之為“飽和”狀態(tài)妆绞。
基于Guava的理論,這是一個線性方程我們可以寫這個形式y = m x + y
;(b.k.y(x))或每秒(q))枫攀,我們預(yù)計每秒給定一個飽和期(eg:3分鐘)括饶,m是變化的速度從我們冷(最小)率穩(wěn)定(最大),x(或q)是被占領(lǐng)的令牌来涨。
下面通過數(shù)學(xué)知識理解:
^ throttling
|
3*stable + /
interval | /.
(cold) | / .
| / . <-- "warmup period" is the area of the trapezoid between
2*stable + / . warningToken and maxToken(預(yù)熱區(qū)為這個梯形區(qū)域)
interval | / .
| / .
| / B .
stable +----------/ WARM . }
interval | . UP . } <-- 這塊矩形 (寬從0至maxPermits, 高為stableInterval
| . PERIOD. } 定義為冷卻區(qū)域图焰,同時我們希望冷卻區(qū)==預(yù)熱區(qū)
| A . . } cooldownPeriod == warmupPeriod
|---------------------------------> storedPermits
(warningToken) (maxToken)
- 當
storedPermits <= warningToken
,那么我用相同的速率消耗它們蹦掐,刷新permits也總是以1/stableInterval
速率生成permits技羔。我們將這塊區(qū)域的大小定位一半的預(yù)熱區(qū)域。為什么我們需要這個卧抗?
為啥是一半藤滥?我們將在下面簡要解釋(在解釋完第二部分之后)。 -
storedPermits
一旦超過warningToken
社裆,將映射到一條從stableInterva
到3倍stableInterval
的提升線拙绊。
這部分的平均高度為2倍stableInterval
(默認codeFactor為3),這塊區(qū)域的大小恰好等于預(yù)熱區(qū)域泳秀。 - 在預(yù)熱區(qū)內(nèi)是獲取令牌的速度是勻速增長的标沪。
-
stableInterval
為qps的時間,即1/count嗜傅。
現(xiàn)在我們看WarmUpController
構(gòu)造方法:
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;
warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
// / maxPermits = thresholdPermits + 2 * warmupPeriod /
// (stableInterval + coldInterval)
// maxToken = 200
maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));
// slope
// slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits
// - thresholdPermits);
slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
}
-
warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1)
;
warningToken
為告警令牌數(shù),warmUpPeriodInSec
及時B區(qū)域的面積磺陡,它是A的面積的coldFactor-1
倍趴梢;stableInterval
為1/count; -
maxToken
是根據(jù)梯形的面積公式計算出來的漠畜; -
slope
是根據(jù)斜率計算公式計算的
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
long passQps = node.passQps();
long previousQps = node.previousPassQps();
syncToken(previousQps);
// 開始計算它的斜率
// 如果進入了警戒線,開始調(diào)整他的qps
long restToken = storedTokens.get();
if (restToken >= warningToken) {
long aboveToken = restToken - warningToken;
// 消耗的速度要比warning快坞靶,但是要比慢
// current interval = restToken*slope+1/count
double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
if (passQps + acquireCount <= warningQps) {
return true;
}
} else {
if (passQps + acquireCount <= count) {
return true;
}
}
return false;
}
- 如果
restToken
進入了警戒線憔狞,開始調(diào)整他的qps
,根據(jù)斜率計算出warningQps
彰阴;若passQps + acquireCount
小于warningQps
則請求通過瘾敢。 - 如果沒有進入警戒線,若
passQps + acquireCount <= count
則請求通過尿这。 - 若1和2不滿足簇抵,則請求不通過。
勻速排隊
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
long currentTime = TimeUtil.currentTimeMillis();
// Calculate the interval between every two requests.
long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
// Expected pass time of this request.
long expectedTime = costTime + latestPassedTime.get();
if (expectedTime <= currentTime) {
// Contention may exist here, but it's okay.
latestPassedTime.set(currentTime);
return true;
} else {
// Calculate the time to wait.
long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
if (waitTime >= maxQueueingTimeMs) {
return false;
} else {
long oldTime = latestPassedTime.addAndGet(costTime);
try {
waitTime = oldTime - TimeUtil.currentTimeMillis();
if (waitTime >= maxQueueingTimeMs) {
latestPassedTime.addAndGet(-costTime);
return false;
}
Thread.sleep(waitTime);
return true;
} catch (InterruptedException e) {
}
}
}
return false;
}
1.根據(jù)qps計算兩次請求的時間間隔并且獲取當前請求的期待時間射众。
2.如果期待時間小于當前時間碟摆,則請求通過;否則先獲取waitTime
叨橱,若waitTime
大于maxQueueingTimeMs
隊列排隊時間典蜕,則請求阻止。
3.通過latestPassedTime.addAndGet(costTime)
加上costTime;若此時waitTime
還大于maxQueueingTimeMs
隊列排隊時間罗洗,latestPassedTime
時間恢復(fù)加costTime之前的值愉舔,并請求阻止;否則線程睡眠waitTime時間伙菜,并請求通過轩缤。
WarmUp + 勻速排隊
這種選擇就是WarmUp與勻速排隊組合,具體可見源碼贩绕。
五火的、我的總結(jié)
1、介紹了Sentinel的限流規(guī)則以及限流原理丧叽。
2卫玖、FlowSlot是整個插槽鏈中最復(fù)雜的一塊,主要根據(jù)了前面 NodeSelectorSlot踊淳、ClusterNodeBuilderSlot假瞬、StatistcSlot 統(tǒng)計出來的實時信息進行流量控制。
3迂尝、閾值類型有兩種(限流閾值類型脱茉,QPS 或線程數(shù)),流控模式有三種(直接垄开、關(guān)聯(lián)琴许、鏈路),流控效果有四種(快速失敗溉躲、WarmUP榜田、排隊等候益兄、WarmUP+排隊等候)。
4箭券、WarmUP限流是根據(jù)Guava的令牌桶算法演變而來的净捅。
以上內(nèi)容,若有不當之處辩块,請指正