一、引子
除了流量控制以外,對調(diào)用鏈路中不穩(wěn)定的資源進行熔斷降級也是保障高可用的重要措施之一。由于調(diào)用關(guān)系的復(fù)雜性,如果調(diào)用鏈路中的某個資源不穩(wěn)定,最終會導(dǎo)致請求發(fā)生堆積。Sentinel 熔斷降級會在調(diào)用鏈路中某個資源出現(xiàn)不穩(wěn)定狀態(tài)時(例如調(diào)用超時或異常比例升高),對這個資源的調(diào)用進行限制,讓請求快速失敗,避免影響到其它的資源而導(dǎo)致級聯(lián)錯誤。當資源被降級后,在接下來的降級時間窗口之內(nèi),對該資源的調(diào)用都自動熔斷(默認行為是拋出 DegradeException)。
二、降級策略
我們通常用以下幾種方式來衡量資源是否處于穩(wěn)定的狀態(tài):
- 平均響應(yīng)時間 (
DEGRADE_GRADE_RT):當資源的平均響應(yīng)時間超過閾值(DegradeRule中的count,以 ms 為單位)之后,資源進入準降級狀態(tài)。接下來如果持續(xù)進入 5 個請求,它們的 RT 都持續(xù)超過這個閾值,那么在接下的時間窗口(DegradeRule中的timeWindow,以 s 為單位)之內(nèi),對這個方法的調(diào)用都會自動地返回(拋出DegradeException)。 - 異常比例 (
DEGRADE_GRADE_EXCEPTION_RATIO):當資源的每秒異??倲?shù)占通過量的比值超過閾值(DegradeRule中的count)之后,資源進入降級狀態(tài),即在接下的時間窗口(DegradeRule中的timeWindow,以 s 為單位)之內(nèi),對這個方法的調(diào)用都會自動地返回。異常比率的閾值范圍是[0.0, 1.0],代表 0% - 100%。 - 異常數(shù) (
DEGRADE_GRADE_EXCEPTION_COUNT):當資源近 1 分鐘的異常數(shù)目超過閾值之后會進行熔斷。
注意:異常降級僅針對業(yè)務(wù)異常,對 Sentinel 限流降級本身的異常(BlockException)不生效。為了統(tǒng)計異常比例或異常數(shù),需要通過 Tracer.trace(ex) 記錄業(yè)務(wù)異常。示例:
Entry entry = null;
try {
entry = SphU.entry(key, EntryType.IN, key);
// Write your biz code here.
// <<BIZ CODE>>
} catch (Throwable t) {
if (!BlockException.isBlockException(t)) {
//這里會統(tǒng)計異常數(shù)
Tracer.trace(t);
}
} finally {
if (entry != null) {
entry.exit();
}
}
開源整合模塊,如 Sentinel Dubbo Adapter, Sentinel Web Servlet Filter 或
@SentinelResource注解會自動統(tǒng)計業(yè)務(wù)異常,無需手動調(diào)用。
三、源碼分析
3.1 DegradeSlot
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args)
throws Throwable {
//規(guī)則檢查
DegradeRuleManager.checkDegrade(resourceWrapper, context, node, count);
fireEntry(context, resourceWrapper, node, count, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
fireExit(context, resourceWrapper, count, args);
}
}
進入DegradeRuleManager中,可以發(fā)現(xiàn)與前面的限流規(guī)則一樣,這個是用于管理降級的類。
我們重點看下checkDegrade方法。
3.2 DegradeRuleManager
public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count)
throws BlockException {
if (degradeRules == null) {
return;
}
List<DegradeRule> rules = degradeRules.get(resource.getName());
if (rules == null) {
return;
}
for (DegradeRule rule : rules) {
if (!rule.passCheck(context, node, count)) {
throw new DegradeException(rule.getLimitApp());
}
}
}
- degradeRule是對應(yīng)資源的額降級規(guī)則,是一個map。
- 獲取到對應(yīng)資源的降級規(guī)則。
- 調(diào)用Degrade的passCheck檢測是否需要降級。
- 若降級了則拋出DegradeException異常。
3.3 DegradeRule
降級規(guī)則的參數(shù)
- count: RT臨界值或者異常數(shù)、異常比列
- timeWindow:降級的時間間隔,單位秒
- grade:閾值類型RT、異常數(shù)、異常比例
下面看下passCheck方法:
@Override
public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
if (cut) {
return false;
}
ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource());
if (clusterNode == null) {
return true;
}
if (grade == RuleConstant.DEGRADE_GRADE_RT) {
double rt = clusterNode.avgRt();
if (rt < this.count) {
passCount.set(0);
return true;
}
// Sentinel will degrade the service only if count exceeds.
if (passCount.incrementAndGet() < RT_MAX_EXCEED_N) {
return true;
}
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
double exception = clusterNode.exceptionQps();
double success = clusterNode.successQps();
long total = clusterNode.totalQps();
// if total qps less than RT_MAX_EXCEED_N, pass.
if (total < RT_MAX_EXCEED_N) {
return true;
}
double realSuccess = success - exception;
if (realSuccess <= 0 && exception < RT_MAX_EXCEED_N) {
return true;
}
if (exception / success < count) {
return true;
}
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
double exception = clusterNode.totalException();
if (exception < count) {
return true;
}
}
synchronized (lock) {
if (!cut) {
// Automatically degrade.
cut = true;
ResetTask resetTask = new ResetTask(this);
pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS);
}
return false;
}
}
關(guān)鍵參數(shù):
- cut:資源是否已經(jīng)降級標志,為true表示已經(jīng)降級了。
- passCount:若達到降級條件后,連續(xù)復(fù)合降級條件的次數(shù),默認為RT_MAX_EXCEED_N(5)次。
過程大致如下:
- 如果已經(jīng)降級了(cut為ture),則阻塞;否則獲取
clusterNode。 - 降級規(guī)則為
RuleConstant.DEGRADE_GRADE_RT,先獲取資源的平均RT;若RT小于設(shè)置的閾值count,則請求通過并設(shè)置passCount為0,否在判斷passCount是否小于5,若小于則請求通過;否則請求阻塞。 - 降級規(guī)則為
RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO異常比例時,先獲取資源的exception數(shù),success數(shù),total數(shù)。若total數(shù)小于5請求通過;exception是小于5請求通過;異常比列exception/total小于設(shè)置的閾值則請求通過;否則請求阻塞。 - 降級規(guī)則為
RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT異常數(shù)時,若異常數(shù)小于設(shè)置的閾值時則請求通過;否則請求阻塞。 - 若上述有規(guī)則不滿足,則說明該資源需要降級;降級時需要先設(shè)置cut為true,并啟動一個定時任務(wù)來設(shè)置降級時間窗口后降級的重置。該任務(wù)如下:
private static final class ResetTask implements Runnable {
private DegradeRule rule;
ResetTask(DegradeRule rule) {
this.rule = rule;
}
@Override
public void run() {
//設(shè)置passCount為0
rule.getPassCount().set(0);
//設(shè)置cut為false
rule.setCut(false);
}
}
四、我的總結(jié)
- 介紹了Sentinel的j降級規(guī)則以及降級原理。
- 降級有三種策略,rt,異常數(shù),異常比例;目前dashboard控制臺能夠設(shè)置應(yīng)該就只有rt和異常比例了。
- 通過設(shè)置passCount來避免出現(xiàn)偶爾一個請求異常的情況,提高降級的準確性。
- 生產(chǎn)環(huán)境下建議使用設(shè)置rt策略來控制降級。
以上內(nèi)容,若有不當之處,請指正。