Spring Cloud Alibaba——Sentinel SlotChain鏈流程總覽

前言

Sentinel作為ali開源的一款輕量級流控框架,主要以流量為切入點(diǎn),從流量控制、熔斷降級、系統(tǒng)負(fù)載保護(hù)等多個(gè)維度來幫助用戶保護(hù)服務(wù)的穩(wěn)定性。相比于Hystrix,Sentinel的設(shè)計(jì)更加簡單,在 Sentinel中資源定義和規(guī)則配置是分離的,也就是說用戶可以先通過Sentinel API給對應(yīng)的業(yè)務(wù)邏輯定義資源(埋點(diǎn)),然后在需要的時(shí)候再配置規(guī)則,通過這種組合方式,極大的增加了Sentinel流控的靈活性。

引入Sentinel帶來的性能損耗非常小。只有在業(yè)務(wù)單機(jī)量級超過25W QPS的時(shí)候才會有一些顯著的影響(5% - 10% 左右),單機(jī)QPS不太大的時(shí)候損耗幾乎可以忽略不計(jì)。

Sentinel提供兩種埋點(diǎn)方式:

  • try-catch 方式(通過 SphU.entry(...)),用戶在 catch 塊中執(zhí)行異常處理 / fallback

  • if-else 方式(通過 SphO.entry(...)),當(dāng)返回 false 時(shí)執(zhí)行異常處理 / fallback

寫在前面

在此之前,需要先了解一下Sentinel的工作流程。

在 Sentinel 里面,所有的資源都對應(yīng)一個(gè)資源名稱(resourceName),每次資源調(diào)用都會創(chuàng)建一個(gè) Entry 對象。Entry 可以通過對主流框架的適配自動創(chuàng)建,也可以通過注解的方式或調(diào)用 SphU API 顯式創(chuàng)建。Entry 創(chuàng)建的時(shí)候,同時(shí)也會創(chuàng)建一系列功能插槽(slot chain),這些插槽有不同的職責(zé),例如默認(rèn)情況下會創(chuàng)建以下7個(gè)插槽:

  • NodeSelectorSlot:負(fù)責(zé)收集資源的路徑,并將這些資源的調(diào)用路徑,以樹狀結(jié)構(gòu)存儲起來,用于根據(jù)調(diào)用路徑來限流降級。

  • ClusterBuilderSlot:則用于存儲資源的統(tǒng)計(jì)信息以及調(diào)用者信息,例如該資源的 RT, QPS, thread count 等等,這些信息將用作為多維度限流,降級的依據(jù)。

  • StatisticSlot:則用于記錄、統(tǒng)計(jì)不同緯度的 runtime 指標(biāo)監(jiān)控信息。

  • SystemSlot:則通過系統(tǒng)的狀態(tài),例如 load1 等,來控制總的入口流量。

  • AuthoritySlot:則根據(jù)配置的黑白名單和調(diào)用來源信息,來做黑白名單控制。

  • FlowSlot:則用于根據(jù)預(yù)設(shè)的限流規(guī)則以及前面 slot 統(tǒng)計(jì)的狀態(tài),來進(jìn)行流量控制。

  • DegradeSlot:則通過統(tǒng)計(jì)信息以及預(yù)設(shè)的規(guī)則,來做熔斷降級。

注意:這里的插槽鏈都是一一對應(yīng)資源名稱的。

每個(gè)Slot執(zhí)行完業(yè)務(wù)邏輯處理后,會調(diào)用fireEntry()方法,該方法將會觸發(fā)下一個(gè)節(jié)點(diǎn)的entry方法,下一個(gè)節(jié)點(diǎn)又會調(diào)用他的fireEntry,以此類推直到最后一個(gè)Slot,由此就形成了Sentinel的責(zé)任鏈。

上面的所介紹的插槽(slot chain)是Sentinel非常重要的概念。同時(shí)還有一個(gè)非常重要的概念那就是Node。

Node之間的樹形結(jié)構(gòu)

在創(chuàng)建context會先創(chuàng)建DefaultNode 實(shí)際是它的父類EntranceNode,context可以相同context-name反復(fù)申明創(chuàng)建,但是DefaultNode同一context-name只會創(chuàng)建一次,DefaultNode包含了一個(gè)鏈路所有的資源,每一個(gè)資源對應(yīng)一個(gè)ClusterNode,ClusterNode再根據(jù)來源細(xì)分為StatisticNode,它們之間的關(guān)系就是一個(gè)樹形結(jié)構(gòu) 如下:

  • EntranceNode:根據(jù)context-name來創(chuàng)建,就算同一個(gè)context-name多次創(chuàng)建context,entranceNode也只會創(chuàng)建一次, 用來統(tǒng)計(jì)該鏈路上所有的資源信息。

  • DefaultNode:根據(jù)context-name + resource-name創(chuàng)建,用來統(tǒng)計(jì)某鏈路上的資源信息。

  • ClusterNode:根據(jù)resource-name來創(chuàng)建,用來統(tǒng)計(jì)資源信息。

  • StatisticsNode:根據(jù)origin-name+resource-name來創(chuàng)建,針對請求來源統(tǒng)計(jì)該來源的資源信息,上面幾個(gè)node都是它的子類,基于它的數(shù)據(jù)做匯總。

一定要搞清楚這幾個(gè)node之間的關(guān)系和作用,下面重點(diǎn)來看StatisticsNode,它用來完成信息統(tǒng)計(jì),以供后續(xù)的限流規(guī)則使用, 它只統(tǒng)計(jì)了兩個(gè)維度數(shù)據(jù),qps和線程數(shù)。

入門案例

  • 引入maven依賴
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <!--整合Spring Cloud Alibaba-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.6.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement> 
  • 下面舉一個(gè)最簡單的案例埋點(diǎn)來引出流控入口
public String getOrderInfo(String orderNo) {
    ContextUtil.enter("getOrderInfo", "application-a");
    Entry entry = null;
    try {
        // name:資源名 EntryType 流量類型為入口還是出口,系統(tǒng)規(guī)則只針對入口流量, batchCount:當(dāng)前請求流量, args:參數(shù)
        entry = SphU.entry("getOrderInfo", EntryType.IN, 1, orderNo);
        getUserInfo();
    } catch (BlockException e) {
        e.printStackTrace();
    } finally {
        entry.exit();
    }
    return "orderInfo = " + orderNo;
}

public String getUserInfo() {
    Entry entry = null;
    try {
        entry = SphU.entry("getUserInfo", EntryType.OUT, 1);
        // 通過http或feign對用戶服務(wù)完成該接口調(diào)用
    } catch (BlockException e) {
        e.printStackTrace();
    } finally {
        entry.exit();
    }
    return "userInfo";
}

public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException 

也可以通過注解的方式引入,執(zhí)行方法時(shí)SentinelResourceAspect會做攔截進(jìn)行流控處理,當(dāng)然什么都不配也是可以的,因?yàn)橐雜pring-cloud-starter-alibaba-sentinel包spring mvc和spring webflux做了適配,自動會對每一個(gè)請求做埋點(diǎn)

@GetMapping("getOrderInfo")
@SentinelResource(value = "/getOrderInfo", entryType = EntryType.IN)
public String getOrderInfo(@RequestParam("orderNo") String orderNo) {
    return "orderInfo = " + orderNo;
}

ContextUtil.enter("getOrderInfo", "application-a") 來表示調(diào)用鏈的入口,可以暫時(shí)理解為上下文,一般不做聲明后面會默認(rèn)創(chuàng)建。

第一個(gè)參數(shù)為context-name,區(qū)分不同的調(diào)用鏈入口,默認(rèn)常量值sentinel_default_context。

第二參數(shù)為調(diào)用來源,這個(gè)參數(shù)可以細(xì)分從不同應(yīng)用來源發(fā)出的請求,授權(quán)規(guī)則白名單和黑名單會根據(jù)該參數(shù)做限制,然后通過SphU.entry()埋點(diǎn)進(jìn)入,下面說下這個(gè)方法幾個(gè)參數(shù)的含義:

  • name:當(dāng)前資源名。
  • trafficType:流量類型 分別為入口流量和出口流量。入口流量和出口流量執(zhí)行過程都是差不多,只是入口流量會多了一個(gè)系統(tǒng)規(guī)則攔截,像是上面案例從訂單服務(wù)調(diào)用getUserInfo,getUserInfo是去調(diào)用用戶服務(wù),它的流量方式是出去的,壓力都在用戶服務(wù)那邊,不用考慮當(dāng)前服務(wù)器的壓力,所以標(biāo)為出口流量。
  • batchCount:當(dāng)前流量數(shù)量,一般默認(rèn)為1。
  • args:參數(shù),后面做熱點(diǎn)參數(shù)規(guī)則時(shí)用到。

BlockException:當(dāng)某一規(guī)則不通過時(shí)會拋出對應(yīng)異常。

SphU.entry(xxx) 需要與 entry.exit() 方法成對出現(xiàn),匹配調(diào)用,如有嵌套像上面,需先退出getUserInfo的entry在退出getOrderInfo的entry

打開打控制臺,此時(shí)應(yīng)該是空白的,sentinel控制臺是懶加載模式,需要調(diào)用一下相關(guān)資源接口就可以看到


可以看到sentinel規(guī)則配置主要有流控規(guī)則,降級規(guī)則,熱點(diǎn)規(guī)則,系統(tǒng)規(guī)則,授權(quán)規(guī)則,先簡單介紹下規(guī)則作用,其它配置也很簡單 一目了然,后面通過結(jié)合源碼來深入分析:

  • 流控規(guī)則:針對資源流量控制。
  • 熱點(diǎn)規(guī)則:針對資源的熱點(diǎn)參數(shù)做流量控制。
  • 降級規(guī)則:針對資源的調(diào)度情況來做降級處理。
  • 系統(tǒng)規(guī)則:針對當(dāng)前服務(wù)做全局流量控制。
  • 授權(quán)規(guī)則:對訪問資源的特定應(yīng)用做授權(quán)處理。

源碼分析

從上面的Sentinel使用的示例代碼,我們就從這里切入開始分析

ContextUtil.enter()

public class ContextUtil {

    /**
     * Store the context in ThreadLocal for easy access.
     */
    private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();

    /**
     * Holds all {@link EntranceNode}. Each {@link EntranceNode} is associated with a distinct context name.
     */
    private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();

    public static Context enter(String name, String origin) {
        // 判斷上下文名稱是否為默認(rèn)的名稱(sentinel_default_context) 是的話直接拋出異常
        if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
            throw new ContextNameDefineException(
                "The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!");
        }
        return trueEnter(name, origin);
    }
    
    protected static Context trueEnter(String name, String origin) {
        // 先從ThreadLocal中嘗試獲取,獲取到則直接返回
        Context context = contextHolder.get();
        if (context == null) {
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            // 嘗試從緩存中獲取該上下文名稱對應(yīng)的 入口節(jié)點(diǎn)
            DefaultNode node = localCacheNameMap.get(name);
            if (node == null) {
                // 判斷緩存中入口節(jié)點(diǎn)數(shù)量是否大于2000
                if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                    setNullContext();
                    return NULL_CONTEXT;
                } else {
                    try {
                        // 加鎖
                        LOCK.lock();
                        node = contextNameNodeMap.get(name);
                        // 雙重檢查鎖
                        if (node == null) {
                            // 判斷緩存中入口節(jié)點(diǎn)數(shù)量是否大于2000
                            if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                                setNullContext();
                                return NULL_CONTEXT;
                            } else {
                                // 根據(jù)上下文名稱生成入口節(jié)點(diǎn)(entranceNode)
                                node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                // Add entrance node.
                                // 加入至全局根節(jié)點(diǎn)下
                                Constants.ROOT.addChild(node);
                                // 加入緩存中
                                Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                                newMap.putAll(contextNameNodeMap);
                                newMap.put(name, node);
                                contextNameNodeMap = newMap;
                            }
                        }
                    } finally {
                        LOCK.unlock();
                    }
                }
            }
            // 初始化上下文對象
            context = new Context(node, name);
            context.setOrigin(origin);
            // 設(shè)置到當(dāng)前線程中
            contextHolder.set(context);
        }

        return context;
    }   
}

主要做了2件事情:

  • 1、根據(jù)ContextName生成entranceNode,并加入緩存,每個(gè)ContextName對應(yīng)一個(gè)入口節(jié)點(diǎn)entranceNode。

  • 2、根據(jù)ContextName和entranceNode初始化上下文對象,并將上下文對象設(shè)置到當(dāng)前線程中。

這里有幾點(diǎn)需要注意:

  • 1、入口節(jié)點(diǎn)數(shù)量不能大于2000,大于會直接拋異常。
  • 2、每個(gè)ContextName對應(yīng)一個(gè)入口節(jié)點(diǎn)entranceNode。
  • 3、每個(gè)entranceNode都有共同的父節(jié)點(diǎn)。也就是根節(jié)點(diǎn)。

SphU.entry(xxx)執(zhí)行過程分析

public class SphU {

    private static final Object[] OBJECTS0 = new Object[0];
    
    public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args)
        throws BlockException {
        // 默認(rèn)為 出口流量類型,單位統(tǒng)計(jì)數(shù)為1
        return Env.sph.entry(name, trafficType, batchCount, args);
    }   
}

public class CtSph implements Sph {

    @Override
    public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
        // 生成資源對象
        StringResourceWrapper resource = new StringResourceWrapper(name, type);
        return entry(resource, count, args);
    }
    
    public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
        return entryWithPriority(resourceWrapper, count, false, args);
    }   
}

上面的代碼比較簡單,不指定EntryType的話,則默認(rèn)為出口流量類型,最終會調(diào)用entryWithPriority方法,主要業(yè)務(wù)邏輯也都在這個(gè)方法中

entryWithPriority方法

public class CtSph implements Sph {

    private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        // 獲取當(dāng)前線程上下文對象
        Context context = ContextUtil.getContext();
        // 上下文名稱對應(yīng)的入口節(jié)點(diǎn)是否已經(jīng)超過閾值2000,超過則會返回空 CtEntry
        if (context instanceof NullContext) {
            return new CtEntry(resourceWrapper, null, context);
        }

        if (context == null) {
            // 如果沒有指定上下文名稱,則使用默認(rèn)名稱,也就是默認(rèn)入口節(jié)點(diǎn)
            context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
        }

        // 全局開關(guān)
        if (!Constants.ON) {
            return new CtEntry(resourceWrapper, null, context);
        }
        
        // 生成插槽鏈
        ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

        /*
         * 表示資源(插槽鏈)超過6000,因此不會進(jìn)行規(guī)則檢查。
         */
        if (chain == null) {
            return new CtEntry(resourceWrapper, null, context);
        }
        
         // 生成 Entry 對象
         // 創(chuàng)建一個(gè)流量入口,將context curEntry進(jìn)行指定
        Entry e = new CtEntry(resourceWrapper, chain, context);
        try {
            // 開始執(zhí)行插槽鏈 調(diào)用邏輯
            chain.entry(context, resourceWrapper, null, count, prioritized, args);
        } catch (BlockException e1) {
            // 發(fā)生流控異常進(jìn)行退出 清除上下文
            e.exit(count, args);
            // 將異常向上拋
            throw e1;
        } catch (Throwable e1) {
            // 除非Sentinel內(nèi)部存在錯(cuò)誤,否則不應(yīng)發(fā)生這種情況。
            RecordLog.info("Sentinel unexpected exception", e1);
        }
        return e;
    }   
}

這個(gè)方法可以說是涵蓋了整個(gè)Sentinel的核心邏輯

  • 1、獲取上下文對象,如果上下文對象還未初始化,則使用默認(rèn)名稱初始化。初始化邏輯在上文已經(jīng)分析過。
  • 2、判斷全局開關(guān)。
  • 3、根據(jù)給定的資源生成插槽鏈,插槽鏈?zhǔn)歉Y源相關(guān)的,Sentinel最關(guān)鍵的邏輯也都在各個(gè)插槽中。初始化的邏輯在lookProcessChain(resourceWrapper);中,下面會分析。
  • 4、依順序執(zhí)行每個(gè)插槽邏輯。

lookProcessChain(resourceWrapper)方法

lookProcessChain方法為指定資源生成插槽鏈,下面我們來看下它的初始化邏輯

public class CtSph implements Sph {

    private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
        = new HashMap<ResourceWrapper, ProcessorSlotChain>();

    ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
        // 根據(jù)資源嘗試從全局緩存中獲取
        ProcessorSlotChain chain = chainMap.get(resourceWrapper);
        if (chain == null) {
            // 非常常見的雙重檢查鎖
            synchronized (LOCK) {
                chain = chainMap.get(resourceWrapper);
                if (chain == null) {
                    // 判斷資源數(shù)是否大于6000
                    if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                        return null;
                    }
                    // 初始化插槽鏈
                    chain = SlotChainProvider.newSlotChain();
                    Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                        chainMap.size() + 1);
                    newMap.putAll(chainMap);
                    newMap.put(resourceWrapper, chain);
                    chainMap = newMap;
                }
            }
        }
        return chain;
    }
}
  • 1、根據(jù)資源嘗試從全局緩存中獲取插槽鏈。每個(gè)資源對應(yīng)一個(gè)插槽鏈(資源最多只能定義6000個(gè))

  • 2、初始化插槽鏈上的插槽(SlotChainProvider.newSlotChain()方法中)

下面我們看下初始化插槽鏈上的插槽的邏輯

SlotChainProvider.newSlotChain()

public final class SlotChainProvider {

    private static volatile SlotChainBuilder slotChainBuilder = null;
    
    public static ProcessorSlotChain newSlotChain() {
        // 判斷是否已經(jīng)初始化過
        if (slotChainBuilder != null) {
            return slotChainBuilder.build();
        }

        // Resolve the slot chain builder SPI.
        // SPI獲取構(gòu)造器
        slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
        // 加載失敗則使用默認(rèn) 插槽鏈 
        if (slotChainBuilder == null) {
            // Should not go through here.
            RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
            slotChainBuilder = new DefaultSlotChainBuilder();
        } else {
            RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
                slotChainBuilder.getClass().getCanonicalName());
        }
        // 構(gòu)建完成
        return slotChainBuilder.build();
    }
}

public final class SpiLoader<S> {

    private static final String SPI_FILE_PREFIX = "META-INF/services/";
    
    //初始化spiLoader類加載器
    public static <T> SpiLoader<T> of(Class<T> service) {
        AssertUtil.notNull(service, "SPI class cannot be null");
        AssertUtil.isTrue(service.isInterface() || Modifier.isAbstract(service.getModifiers()),
                "SPI class[" + service.getName() + "] must be interface or abstract class");

        String className = service.getName();
        SpiLoader<T> spiLoader = SPI_LOADER_MAP.get(className);
        if (spiLoader == null) {
            synchronized (SpiLoader.class) {
                spiLoader = SPI_LOADER_MAP.get(className);
                if (spiLoader == null) {
                    SPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service));
                    spiLoader = SPI_LOADER_MAP.get(className);
                }
            }
        }

        return spiLoader;
    }   
    
    public S loadFirstInstanceOrDefault() {
        //通過spiLoader去 META-INF/services/目錄下去加載文件
        load();

        for (Class<? extends S> clazz : classList) {
            if (defaultClass == null || clazz != defaultClass) {
                return createInstance(clazz);
            }
        }

        return loadDefaultInstance();
    }
}

SPI獲取構(gòu)造器——spiLoader類加載器,通過spiLoader去 META-INF/services/目錄下去加載文件DefaultSlotChainBuilder。

下面是DefaultSlotChainBuilder的build方法:

@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        // 先創(chuàng)建一個(gè)default作為header
        // chain是一個(gè)單向鏈表 first -> next -> end    
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        
        // SPI機(jī)制——即SpiLoader在META-INF/services/目錄下去加載文件 找到所有slot的實(shí)現(xiàn),并根據(jù)@SpiOrder注解排序
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            // 剔除掉Abstract類
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue;
            }
            //addLast方法往鏈表的最后追加Slot對象。
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }

        return chain;
    }
}

首先初始化一個(gè)DefaultProcessorSlotChain,DefaultProcessorSlotChain里面維護(hù)了一個(gè)Slot的單向鏈表。

然后利用Java SPI機(jī)制——即SpiLoader在META-INF/services/目錄下去加載文件,找到所有slot的實(shí)現(xiàn),并根據(jù)@SpiOrder注解排序, 最后調(diào)用addLast方法往鏈表的最后追加Slot對象。

public class DefaultProcessorSlotChain extends ProcessorSlotChain {

    AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {

        @Override
        public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
            throws Throwable {
            super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
        }

        @Override
        public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
            super.fireExit(context, resourceWrapper, count, args);
        }

    };
    AbstractLinkedProcessorSlot<?> end = first;

    @Override
    public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        end.setNext(protocolProcessor);
        end = protocolProcessor;
    }
}


public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
    
    public void setNext(AbstractLinkedProcessorSlot<?> next) {
        this.next = next;
    }
}

至此SlotChain構(gòu)造完成。

執(zhí)行SlotChain鏈

SlotChain鏈執(zhí)行的入口是 chain.entry(context, resourceWrapper, null, count, prioritized, args);這行

com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain#entry

public class DefaultProcessorSlotChain extends ProcessorSlotChain {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
        throws Throwable {
        first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
    }
}

就是執(zhí)行first的transformEntry

public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {

    void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
        throws Throwable {
        T t = (T)o;
        entry(context, resourceWrapper, t, count, prioritized, args);
    }
}

first的entry方法

public class DefaultProcessorSlotChain extends ProcessorSlotChain {
    AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
        public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args) throws Throwable {
            super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
        }

        public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
            super.fireExit(context, resourceWrapper, count, args);
        }
    };
}

父類的fireEntry

public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {

    private AbstractLinkedProcessorSlot<?> next = null;

    @Override
    public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        if (next != null) {
            next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
        }
    }
}

可以看到,實(shí)際上就是執(zhí)行了next的transformEntry,而next的transformEntry又會調(diào)用它的entry方法,entry方法由每個(gè)Slot自己實(shí)現(xiàn),只要在里面調(diào)用fireEntry,即觸發(fā)next的next的entry調(diào)用。

這樣繼續(xù)下去,直到next為空

簡單畫個(gè)圖描述一下這個(gè)過程:


exit()方法和entry方法類似,這里就不分析了。

至此SlotChain調(diào)用鏈分析完了,總結(jié)一下

  • 1、Chain中維護(hù)了一個(gè)單向鏈表
  • 2、通過fileEntry觸發(fā)next的entry調(diào)用。
  • 3、責(zé)任鏈模式

Sentinel中預(yù)設(shè)的SlotChain執(zhí)行的完整流程:

參考:
https://www.cnblogs.com/taromilk/p/11750962.html

https://www.cnblogs.com/zzz-blogs/p/14342608.html

https://blog.csdn.net/qq_19414183/article/details/111035989

https://blog.csdn.net/wk52525/article/details/104439404

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容