Sentinel源碼解析二(Slot總覽)


寫在前面

本文繼續來分析Sentinel的源碼,上篇文章對Sentinel的調用過程做了深入分析,主要涉及到了兩個概念:插槽鏈和Node節點。那么接下來我們就根據插槽鏈的調用關系來依次分析每個插槽(slot)的源碼。

默認插槽鏈的調用順序,以及每種類型Node節點的關系都在上面文章開頭分析過 Sentinel源碼解析一

NodeSelectorSlot

/**
 * 相同的資源但是Context不同,分別新建 DefaultNode,並以ContextName為key
 */
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);

public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
    throws Throwable {
    
    // 根據ContextName嘗試獲取DefaultNode
    DefaultNode node = map.get(context.getName());
    if (node == null) {
        synchronized (this) {
            node = map.get(context.getName());
            if (node == null) {
            // 初始化DefaultNode,每個Context對應一個
                node = new DefaultNode(resourceWrapper, null);
                HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                cacheMap.putAll(map);
                cacheMap.put(context.getName(), node);
                map = cacheMap;
            }
            // 構建 Node tree
            ((DefaultNode)context.getLastNode()).addChild(node);
        }
    }

    context.setCurNode(node);
    // 喚醒執行下一個插槽
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

NodeSelectorSlot顧名思義是用來構建Node的。
我們可以看到NodeSelectorSlot對於不同的上下文都會生成一個DefaultNode。這里還有一個要注意的點:相同的資源({@link ResourceWrapper#equals(Object)})將全局共享相同的{@link ProcessorSlotChain},無論在哪個上下文中,因此不同的上下文可以進入到同一個對象的NodeSelectorSlot.entry方法中,那么這里要怎么區分不同的上下文所創建的資源Node呢?顯然可以使用上下文名稱作為映射鍵以區分相同的資源Node。

然后這里要考慮另一個問題。一個資源有可能創建多個DefaultNode(有多個上下文時),那么我們應該如何快速的獲取總的統計數據呢?

答案就在下一個Slot(ClusterBuilderSlot)中被解決了。

ClusterBuilderSlot

上面有提到一個問題,我們要如何統計不同上下文相同資源的總量數據。ClusterBuilderSlot給了很好的解決方案:具有相同資源名稱的共享一個ClusterNode

// 相同的資源共享一個 ClusterNode
private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();

private static final Object lock = new Object();

private volatile ClusterNode clusterNode = null;

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args)
    throws Throwable {
    // 判斷本資源是否已經初始化過clusterNode
    if (clusterNode == null) {
        synchronized (lock) {
            if (clusterNode == null) {
                // 沒有初始化則初始化clusterNode
                clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
                HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
                newMap.putAll(clusterNodeMap);
                newMap.put(node.getId(), clusterNode);

                clusterNodeMap = newMap;
            }
        }
    }
    // 給相同資源的DefaultNode設置一樣的ClusterNode
    node.setClusterNode(clusterNode);

    /*
     * 如果有來源則新建一個來源Node
     */
    if (!"".equals(context.getOrigin())) {
        Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
        context.getCurEntry().setOriginNode(originNode);
    }

    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

上面的代碼其實就是做了一件事情,為資源創建CluserNode。這里我又要提一嘴 相同的資源({@link ResourceWrapper#equals(Object)})將全局共享相同的{@link ProcessorSlotChain},無論在哪個上下文中。也就是說,能進入到同一個ClusterBuilderSlot對象的entry方法的請求都是來自同一個資源的,所以這些相同資源需要初始化一個統一的CluserNode用來做流量的匯總統計。

LogSlot

代碼比較簡單,邏輯就是打印異常日志,就不分析了

StatisticSlot

StatisticSlot 是 Sentinel 的核心功能插槽之一,用於統計實時的調用數據。

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args) throws Throwable {
    try {
        // 先進行后續的check,包括規則的check,黑白名單check
        fireEntry(context, resourceWrapper, node, count, prioritized, args);

        // 統計默認qps 線程數
        node.increaseThreadNum();
        node.addPassRequest(count);

        if (context.getCurEntry().getOriginNode() != null) {
            // 根據來源統計qps 線程數
            context.getCurEntry().getOriginNode().increaseThreadNum();
            context.getCurEntry().getOriginNode().addPassRequest(count);
        }

        if (resourceWrapper.getEntryType() == EntryType.IN) {
            // 統計入口 qps 線程數
            Constants.ENTRY_NODE.increaseThreadNum();
            Constants.ENTRY_NODE.addPassRequest(count);
        }

        .... 省略其他代碼
    }
}

StatisticSlot主要做了4種不同維度的流量統計

  1. 資源在上下文維度(DefaultNode)的統計
  2. clusterNode 維度的統計
  3. Origin 來源維度的統計
  4. 入口全局流量的統計

關於流量的統計原理的本文就不深入分析了,接下來的文章中會單獨分析

SystemSlot

SystemSlot比較簡單,其實就是根據StatisticSlot所統計的全局入口流量進行限流。

AuthoritySlot

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
    throws Throwable {
    checkBlackWhiteAuthority(resourceWrapper, context);
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
    Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();

    if (authorityRules == null) {
        return;
    }
	  // 根據資源獲取黑白名單規則
    Set<AuthorityRule> rules = authorityRules.get(resource.getName());
    if (rules == null) {
        return;
    }
    // 對規則進行校驗,只要有一條不通過 就拋異常
    for (AuthorityRule rule : rules) {
        if (!AuthorityRuleChecker.passCheck(rule, context)) {
            throw new AuthorityException(context.getOrigin(), rule);
        }
    }
}

AuthoritySlot會對資源的黑白名單做檢查,並且只要有一條不通過就拋異常。

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 {
    // 檢查限流規則
    checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
}

這個slot主要根據預設的資源的統計信息,按照固定的次序,依次生效。如果一個資源對應兩條或者多條流控規則,則會根據如下次序依次檢驗,直到全部通過或者有一個規則生效為止:
並且同樣也會根據三種不同的維度來進行限流:

  1. 資源在上下文維度(DefaultNode)的統計
  2. clusterNode 維度的統計
  3. Origin 來源維度的統計

關於流控規則源碼的深入分析就不在本篇文章贅述了

DegradeSlot

這個slot主要針對資源的平均響應時間(RT)以及異常比率,來決定資源是否在接下來的時間被自動熔斷掉。

總結

  1. 相同的資源({@link ResourceWrapper#equals(Object)})將全局共享相同的{@link ProcessorSlotChain},無論在哪個上下文中
  2. 流控有多個維度,分別包括:1.不同上下文中的資源 2.相同資源 3.入口流量 3.相同的來源

Sentinel系列


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM