前言
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