Sentinel系列之SlotChain、NodeSelectorSlot、ClusterBuilderSlot分析

kingsleylam發表於2023-10-04

本文基於Sentinel 1.8.6版本分析

1. SlotChain

我們從入口com.alibaba.csp.sentinel.SphU#entry(java.lang.String) 開始分析。

一路走讀下來,會進入到這個方法com.alibaba.csp.sentinel.CtSph#lookProcessChain,查詢該資源對應的Slot Chain。

image

接下來看如何構建這個Slot Chain. Sentinel實現了自己的一套SPI機制,提供了快取和排序等功能。在@Spi註解上有一個order欄位,支援按order從小到大排序。

image

image

2. Slot

插槽的介面叫ProcessorSlot,它有4個方法,分別對應入口、出口時自己執行邏輯及呼叫下一個Slot的入口或出口。

image

Slot Chain也是插槽的一個實現,作為鏈條的入口 ,比較特殊的點在於它持有了其他的Slot。

image

2.1. NodeSelectorSlot

NodeSelectorSlot 負責收集資源的路徑,並將這些資源的呼叫路徑,以樹狀結構儲存起來,用於根據呼叫路徑來限流降級;

image

首先了解一下Node的類繼承關係

image

有三種不同的節點:

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);
            }
        }
    }

}

透過上面的分析,可以得出如下的呼叫鏈路。

image

使用Debug方式,在呼叫完成後,檢視記憶體中的呼叫鏈路

image

執行結果,只對context1限流

image

需要注意的是,這裡的呼叫關係並不是程式碼的呼叫鏈路,而是關注呼叫入口和資源的關係。

再來看下面這個例子,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("被流控了!");
        }
    }

image

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.

image

所以,完整的關係圖如下,綠色表示這些節點儲存了不同維度的統計資料

image

透過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);
            }
        }
    }

image

透過上面的介紹,我們知道了有不同的Node,並且流量控制有不同的策略Strategy,還有可以透過呼叫來源來限制,那麼不同的情況下會使用什麼節點進行限流判斷呢?

在進行限流判斷前,會呼叫這個方法選擇合適的節點 com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#selectNodeByRequesterAndStrategy

image

image

呼叫來源/策略 Direct Relate Chain
指定來源 origin cluster current(當前節點就是鏈路第三層的DefaultNode)
不指定來源 cluster cluster current(當前節點就是鏈路第三層的DefaultNode)
指定來源之外(Other,其實也是指定來源) origin cluster current(當前節點就是鏈路第三層的DefaultNode)

可以看到,如果選擇了Chain基於鏈路限流,則肯定是使用當前節點,無法指定來源;如果選擇了Relate基於關聯關係,則肯定是使用cluster; 如果選擇了Direct,就要看有沒有指定來源,若指定了則使用origin,否則還是使用cluster.

相關文章