本文基於Sentinel 1.8.6版本分析
1. SlotChain
我們從入口com.alibaba.csp.sentinel.SphU#entry(java.lang.String) 開始分析。
一路走讀下來,會進入到這個方法com.alibaba.csp.sentinel.CtSph#lookProcessChain,查詢該資源對應的Slot Chain。
接下來看如何構建這個Slot Chain. Sentinel實現了自己的一套SPI機制,提供了快取和排序等功能。在@Spi註解上有一個order欄位,支援按order從小到大排序。
2. Slot
插槽的介面叫ProcessorSlot,它有4個方法,分別對應入口、出口時自己執行邏輯及呼叫下一個Slot的入口或出口。
Slot Chain也是插槽的一個實現,作為鏈條的入口 ,比較特殊的點在於它持有了其他的Slot。
2.1. NodeSelectorSlot
NodeSelectorSlot
負責收集資源的路徑,並將這些資源的呼叫路徑,以樹狀結構儲存起來,用於根據呼叫路徑來限流降級;
首先了解一下Node的類繼承關係
有三種不同的節點:
Root: 根節點,全域性唯一,所有呼叫的入口,值固定為machine-root,承載在com.alibaba.csp.sentinel.Constants#ROOT欄位。它的實現類是EntranceNode.
EntranceNode:DefaultNode的子類,入口節點,一個Context會有一個入口節點,用於統計當前Context的總體流量資料,統計維度為Context。可以呼叫com.alibaba.csp.sentinel.context.ContextUtil#enter(java.lang.String)設定,若無設定,預設值為sentinel_default_context。它的實現類也是EntranceNode.
DefaultNode:預設節點,用於統計一個resource在當前Context中的流量資料,DefaultNode持有指定的Context和指定的Resource的統計資料,意味著DefaultNode是以Context和Resource為維度的統計節點。它的實現類是DefaultNode.
透過這個呼叫鏈路,就可以實現基於呼叫鏈路限流。
public class ChainStrategyDemo {
private static final String RESOURCE_1 = "Resource1";
private static final Logger logger = LoggerFactory.getLogger(ChainStrategyDemo.class);
public static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule1 = new FlowRule();
// 繫結資源
rule1.setResource(RESOURCE_1);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 可以只對某個鏈路生效
rule1.setCount(2);
rule1.setStrategy(RuleConstant.STRATEGY_CHAIN);
rule1.setRefResource("context1");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
public static void method(String contextName) {
// 定義資源
try(Entry entry = SphU.entry(RESOURCE_1)){
logger.info("Visit resource 1");
}catch (BlockException e) {
logger.error("{} 被流控了!", contextName);
}
}
public static void main(String[] args) throws InterruptedException {
initFlowRules();
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(new Task("context1"));
executor.submit(new Task("context2"));
Thread.sleep(10_000L);
executor.shutdown();
}
public static class Task implements Runnable{
private String contextName;
public Task(String contextName) {
this.contextName = contextName;
}
@Override
public void run() {
ContextUtil.enter(contextName);
for(int i = 0; i <= 4; i++){
method(contextName);
}
}
}
}
透過上面的分析,可以得出如下的呼叫鏈路。
使用Debug方式,在呼叫完成後,檢視記憶體中的呼叫鏈路
執行結果,只對context1限流
需要注意的是,這裡的呼叫關係並不是程式碼的呼叫鏈路,而是關注呼叫入口和資源的關係。
再來看下面這個例子,method2巢狀了method1。
public static void main(String[] args) throws InterruptedException {
initFlowRules();
method1();
method2();
System.out.println("Finished!");
}
public static void method1() {
// 定義資源
try(Entry entry = SphU.entry(RESOURCE_1)){
System.out.println("Visit resource 1");
}catch (BlockException e) {
System.out.println("被流控了!");
}
}
public static void method2() {
// 定義資源
try(Entry entry = SphU.entry(RESOURCE_2)){
System.out.println("Visit resource 1");
method1();
}catch (BlockException e) {
System.out.println("被流控了!");
}
}
2.2 ClusterBuilderSlot
如果想要以資源的維度來限流,那麼必須對呼叫鏈路的DefaultNode,以資源的維度做一次彙總,ClusterBuilderSlot正是這個作用。
此插槽用於構建資源的 ClusterNode
以及呼叫來源節點。ClusterNode
保持資源執行統計資訊(響應時間、QPS、block 數目、執行緒數、異常數等)以及原始呼叫者統計資訊列表。來源呼叫者的名字由 ContextUtil.enter(contextName,origin)
中的 origin
標記。
ClusterNode: 資源唯一標識的 ClusterNode 的 runtime 統計。它的實現類是ClusterNode.
Origin: 根據來自不同呼叫者的統計資訊,在ClusterNode中有一個Map,專門按Origin呼叫來源統計不同的資料,預設是空字串。在訪問資源前,可以透過com.alibaba.csp.sentinel.context.ContextUtil#enter(java.lang.String, java.lang.String)指定呼叫來源。它的實現類是StatisticNode.
所以,完整的關係圖如下,綠色表示這些節點儲存了不同維度的統計資料
透過LimitApp,就可以實現基於呼叫來源的限流。
public static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule1 = new FlowRule();
// 繫結資源
rule1.setResource(RESOURCE_1);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 可以只對某個來源生效
rule1.setCount(2);
// 預設也是這個值
rule1.setStrategy(RuleConstant.STRATEGY_DIRECT);
rule1.setLimitApp(DEFAULT_LIMIT_APP);
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
public static void method(String origin) {
// 定義資源
try (Entry entry = SphU.entry(RESOURCE_1)) {
logger.info("Visit resource 1");
} catch (BlockException e) {
logger.error("{} 被流控了!", origin);
}
}
public static void main(String[] args) throws InterruptedException {
initFlowRules();
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(new Task(DEFAULT_LIMIT_APP));
executor.submit(new Task("AnotherLimitApp"));
Thread.sleep(10_000L);
executor.shutdown();
}
public static class Task implements Runnable {
private String origin;
public Task(String origin) {
this.origin = origin;
}
@Override
public void run() {
for (int i = 0; i <= 4; i++) {
ContextUtil.enter(DEFAULT_ENTRANCE, origin);
method(origin);
}
}
}
透過上面的介紹,我們知道了有不同的Node,並且流量控制有不同的策略Strategy,還有可以透過呼叫來源來限制,那麼不同的情況下會使用什麼節點進行限流判斷呢?
在進行限流判斷前,會呼叫這個方法選擇合適的節點 com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#selectNodeByRequesterAndStrategy
呼叫來源/策略 | Direct | Relate | Chain |
---|---|---|---|
指定來源 | origin | cluster | current(當前節點就是鏈路第三層的DefaultNode) |
不指定來源 | cluster | cluster | current(當前節點就是鏈路第三層的DefaultNode) |
指定來源之外(Other,其實也是指定來源) | origin | cluster | current(當前節點就是鏈路第三層的DefaultNode) |
可以看到,如果選擇了Chain基於鏈路限流,則肯定是使用當前節點,無法指定來源;如果選擇了Relate基於關聯關係,則肯定是使用cluster; 如果選擇了Direct,就要看有沒有指定來源,若指定了則使用origin,否則還是使用cluster.