在分散式系統中,由於服務數量增多,出現問題的可能性也會增大。想象一下,如果我們上游的系統突然流量增大N倍,超出我們系統承載的流量瞬間進來會不會壓垮我們的系統? 同樣對於我們下游的應用,此刻不知道什麼原因導致RT特別的長,那麼我們系統提供的服務是不是也會收到影響呢?
其它系統都是出於不可控的狀態,想要要求其它系統穩定執行的前提是要保證你自己能穩定執行。因此我們要做的就是不管其它系統如何,我們都穩定執行。那麼如何做呢?市場上經典的工具如Hystrix,當然阿里也提供了一套更加強大和易用的工具,Sentinel。
Sentinel,Hystrix對比:github.com/alibaba/Sen…
結論:因為我們需要保護自己的系統,在流量過大或者下游響應時間過長以及其他意外情況的時候,我們的系統還需要正常執行,雖然市場上有一些其它的工具,但是Sentinel功能更強大,用起來更簡單,擴充套件起來也方便。
Sentinel主要功能:
- 流量控制:將請求調整成合適的形狀。
- 熔斷降級:當依賴的某個資源不穩定的時候,對呼叫的資源進行限制,讓請求快速失敗。
- 系統負載保護:如CPU過高,執行緒池數量太大,則需要對新來的請求做一些限制。
參考:Sentinel是什麼
基本用法
雖然Sentinel提供了很多種使用的方式,但是大部分時候基本的用法就已經滿足我們的要求了。這裡只說下常見的用法。
1 引入依賴,寫Sentinel程式碼
Entry sentinel = null;
try {
sentinel = SphU.entry(request.getName());
// 業務程式碼
}
catch (BlockException blockException) {
log.error("BlockException! request={}", request, blockException);
}
finally {
if (sentinel != null){
sentinel.exit();
}
}
複製程式碼
2 以上只是代表我們有了限流的工具,但是沒有定義具體的規則。通常在dashboard配置規則,不過再程式碼中預定義一些預設規則也可以。
引入Dashboard:github.com/alibaba/Sen…
Sentinel支援的規則有以下幾種:
- 流量控制規則:對應程式碼中的FlowRule
- 熔斷降級規則:對應DegradeRule
- 系統保護規則:對應SystemRule
- 來源訪問控制規則:對應AuthorityRule
- 熱點引數規則:對應ParamFlowRule
每種rule都有自己對應的RuleManager使其生效,比如在程式碼中預定義流量控制規則:
// 定義流量控制規則,限制QPS不高於20
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("HelloWorld");
// set limit qps to 20
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rules.add(rule);
// 將規則載入到記憶體,後續流量控制根據載入的規則進行處理
FlowRuleManager.loadRules(rules);
}
複製程式碼
3 檢視dashboard,或者日誌觀察限流降級是否生效。
- 日誌位置:/家目錄/logs/csp/sentinel相關日誌
- 監控皮膚:可以檢視各種規則當前的狀態
使用方式上,自己摸索下就知道怎麼回事了。就不說太多了。
原理概覽
以以下程式碼為例,Sentinel內部做了什麼操作,來實現限流降級功能呢?
SphU.entry("HelloWorld")
複製程式碼
1 Sentinel針對資源進行操作,entry("HelloWorld")代表要處理名稱為HelloWorld
的資源。操作成功後會返回一個Entry
物件,否則丟擲異常代表不處理當前請求(可以認為是規則限制)
2 Sphu.entry內部關鍵部分程式碼。
// 建立當前呼叫的上下文資訊,為ThreadLocal變數。
// 如果是我們手工建立Context,一般可以指定name與origin(來源)。
// Context內部主要儲存:
// - entranceNode 當前呼叫的入口Node
// - curEntry 當前正在處理的Entry,即每一次呼叫都會生成的Entry物件
// - origin 字串,用來標記來源,統計的時候會用到
Context context = createContext();
// 查詢當前資源的Slot處理鏈,一般一個資源對應一個ProcessorSlotChain,如果沒有找到ProcessorSlotChain會建立預設的DefaultSlotChainBuilder。其中SLot順序如下:
// ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// chain.addLast(new NodeSelectorSlot());
// chain.addLast(new ClusterBuilderSlot());
// chain.addLast(new LogSlot());
// chain.addLast(new StatisticSlot());
// chain.addLast(new AuthoritySlot());
// chain.addLast(new SystemSlot());
// chain.addLast(new FlowSlot());
// chain.addLast(new DegradeSlot());
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
// 建立Entry與SlotChain處理Entry
// 其中依次呼叫上面的Slot進行處理。
Entry e = new CtEntry(resourceWrapper, chain, context);
chain.entry(context, resourceWrapper, null, count, prioritized, args);
複製程式碼
SlotChain處理對應官方的架構圖如下:
- TreeNodeBuilder對應NodeSelectorSlot,主要負責收集資源的路徑,並將這些資源的呼叫路徑,以樹狀結構儲存起來,用於根據呼叫路徑來限流降級
- ClusterNode對應ClusterBuilderSlot,用於儲存資源的統計資訊以及呼叫者資訊,例如該資源的 RT, QPS, thread count 等等,這些資訊將用作為多維度限流,降級的依據;
- StatisticSlot:核心的Slot。各種維度的監控統計位於其中
- FlowSlot,DegradeSlot,AuthoritySlot,SystemSlot:實現方式上基本一致,主要根據統計的資料判斷當前的規則是否生效,看懂了其中一個,其餘的可以舉一反三,當然也可以寫自己自定義的控制規則。
3 根據程式碼,我覺得當前一個比較完整的流程圖應該是:
系統保護原理
上面說了大致流程,看一個具體的規則來了解它是如何生效的。
1 在Sentinel獲取資源的時候,會呼叫SlotChain,正常情況下會走到SystemSlot中進行處理。
2 SystemSlot,交給RuleManager檢查資源是否滿足條件。
SystemRuleManager.checkSystem(resourceWrapper);
複製程式碼
3 規則校驗,可以看到如果系統負載過高,則會丟擲SystemBlockException異常,終止當前的請求處理。
主要程式碼在SystemRuleManager中
...
// load. BBR algorithm.
if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
if (!checkBbr(currentThread)) {
throw new SystemBlockException(resourceWrapper.getName(), "load");
}
}
// cpu usage
if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
throw new SystemBlockException(resourceWrapper.getName(), "cpu");
}
複製程式碼
其它:
- 系統狀態通過SystemStatusListener進行更新,排程器每秒鐘收集一次系統狀態
- 通過SystemRuleManager.loadRules在載入規則
- 系統規則配置實體類為:SystemRule
流控QPS直接拒絕
Sentinel提供了多種流控方式,直接拒絕、Warm Up、勻速排隊。對應 FlowRule
中的 controlBehavior
欄位。
直接拒絕實現比較簡單,通過看它的實現後續再弄明白更復雜的實現。與系統保護類似:
Request -> FlowSlot -> FlowRulechecker -> FlowRuleManager -> FlowRule -> ControlBehavior(TrafficShapingController) -> canPass
小結
這裡主要是說了Sentinel的簡單使用,以及執行過程中程式碼的大體流程,並未深究其資料結構。
程式碼設計上並不算很複雜,如果在使用的過程中遇到什麼問題,點進去看一下大致也能解決。我覺得其核心的程式碼:
- StatisticSlot:多種維度的資料統計入口,在其中使用了LeapArray統計秒級指標資料。
- FlowSlot,DegradeSlot,AuthoritySlot,SystemSlot:熔斷,限流降級邏輯判斷的地方。如果想判斷規則有沒有生效,就到對應的類中Debug下。
更多內容參考:github.com/alibaba/Sen…