經過前面幾篇文章的鋪墊,我們正式來探討 Sentinel 的 entry 方法的實現流程。即探究進入 Alibaba Sentinel 核心的一把鑰匙。
@
無論是從 Sentinel 適配 Dubbo 也好,還是 SphU 源碼中的註釋中能看出,對一個資源進行限流或熔斷,通常需要調用 SphU 的 entry 方法,例如如下示例代碼。
public void foo() {
Entry entry = null;
try {
entry = SphU.entry("abc");
} catch (BlockException blockException) {
// when goes there, it is blocked
// add blocked handle logic here
} catch (Throwable bizException) {
// business exception
Tracer.trace(bizException);
} finally {
// ensure finally be executed
if (entry != null){
entry.exit();
}
}
}
那本文將來探討 SphU.entry 的實現原理。SphU 類定義了很多 entry 重載方法,我們就以下面這個方法爲例來探究其實現原理。
1、SphU.entry 流程分析
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException { // @1
return Env.sph.entry(name, type, count, args); // @2
}
代碼@1:我們先來簡單介紹其核心參數的含義:
- String name
資源的名稱。 - EntryType type
進入資源的方式,主要包含 EntryType.IN、EntryType.OUT。 - int count
可以理解爲本次進入需要消耗的“令牌數”。 - Object... args
其他參數。
代碼@2:調用 Env.sph.entry 的方法,其最終會調用 CtSph 的 entry 方法。
接下來我們將重點查看 CtSph 的 entry 方法。
public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
StringResourceWrapper resource = new StringResourceWrapper(name, type); // @1
return entry(resource, count, args); // @2
}
代碼@1:由於該方法用來表示資源的方式爲一個字符串,故創建一個 StringResourceWrapper 對象來表示一個 Sentinel 中的資源,另外一個實現爲 MethodResourceWrapper,用來表示方法類的資源。
代碼@2:繼續調用 CtSph 的另外一個 entry 重載方法,最終會調用 entryWithPriority 方法。
CtSph#entryWithPriority
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) // @1
throws BlockException {
Context context = ContextUtil.getContext(); // @2
if (context instanceof NullContext) {
return new CtEntry(resourceWrapper, null, context);
}
if (context == null) {
// Using default context.
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
if (!Constants.ON) { // @3
return new CtEntry(resourceWrapper, null, context);
}
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); // @4
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
Entry e = new CtEntry(resourceWrapper, chain, context); // @5
try {
chain.entry(context, resourceWrapper, null, count, prioritized, args); // @6
} catch (BlockException e1) { // @7
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}
代碼@1:我們先來介紹一下該方法的參數:
- ResourceWrapper resourceWrapper
資源的包裝類型,可以是字符串類型的資源描述,也可以是方法類的。 - int count
此次需要消耗的令牌。 - boolean prioritized
是否注重優先級。 - Object... args
額外參數。
代碼@2:獲取方法調用的上下文環境,上下環境對象存儲在線程本地變量:ThreadLocal 中,這裏先“劇透”一下,上下文環境中存儲的是整個調用鏈,後續文章會重點介紹。
代碼@3:Sentinel 提供一個全局關閉的開關,如果關閉,返回的 CtEntry 中的 chain 爲空,從這裏可以看出,如果 chain 爲空,則不會觸發 Sentinel 流控相關的邏輯,從側面也反應了該屬性的重要性。
代碼@4:爲該資源加載處理鏈鏈,這裏是最最重要的方法,將在下文詳細介紹。
代碼@5:根據資源ID、處理器鏈、上下文環境構建 CtEntry 對象。
代碼@6:調用 chain 的 entry 方法。
代碼@7:如果出現 BlockException ,調用 CtEntry 的 exit 方法。
2、Sentienl ProcessorSlot 處理鏈
我們接下來重點看一下 lookProcessChain 方法的實現細節。
CtSph#lookProcessChain
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper); // @1
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit.
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) { // @2
return null;
}
chain = SlotChainProvider.newSlotChain(); // @3
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
代碼@1:chainMap 一個全局的緩存表,即同一個資源 ResourceWrapper (同一個資源名稱) 會共同使用同一個 ProcessorSlotChain ,即不同的線程在訪問同一個資源保護的代碼時,這些線程將共同使用 ProcessorSlotChain 中的各個 ProcessorSlot 。注意留意 ResourceWrapper 的 equals 方法與 hashCode 方法。
代碼@2:這裏重點想突出,如果同時在進入的資源個數超過 MAX_SLOT_CHAIN_SIZE,默認爲 6000,會返回 null,則不對本次請求執行限流,熔斷計算,而是直接跳過,這個點還是值得我們注意的。
代碼@3:通過 SlotChainProvider 創建對應的處理鏈。
SlotChainProvider#newSlotChain
public static ProcessorSlotChain newSlotChain() {
if (slotChainBuilder != null) { // @1
return slotChainBuilder.build();
}
slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class); // @2
if (slotChainBuilder == null) { // @3
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());
}
return slotChainBuilder.build(); // @4
}
代碼@1:如果 slotChainBuilder 不爲空,則直接調用其 build 方法構建處理器鏈。
代碼@2:如果爲空,首先通過 JAVA 的 SPI 機制,嘗試加載自定義的 Slot Chain 構建器實現類。如果需要實現自定義的 Chain 構建器,只需實現 SlotChainBuilder 接口,然後將其放在 classpath 下即可,如果存在多個,以找到的第一個爲準。
代碼@3:如果從 SPI 機制中加載失敗,則使用默認的構建器:DefaultSlotChainBuilder。
代碼@4:調用其 build 方法構造 Slot Chain。
那接下來我們先來看看 Sentinel 的 SlotChainBuilder 類體系,然後看看 DefaultSlotChainBuilder 的 build 方法。
2.1 SlotChainBuilder 類體系
主要有三個實現類,對應熱點、接口網關以及普通場景。我們接下來將重點介紹 DefaultSlotChainBuilder ,關於熱點限流與網關限流將在後面的文章中詳細探討。
2.2 DefaultSlotChainBuilder build 方法
DefaultSlotChainBuilder#build
public class DefaultSlotChainBuilder implements SlotChainBuilder {
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new LogSlot());
chain.addLast(new StatisticSlot());
chain.addLast(new AuthoritySlot());
chain.addLast(new SystemSlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());
return chain;
}
}
就問大家激不激動,開不開心,從這些 Slot 的名字基本就能得出其含義。
- NodeSelectorSlot
主要用於構建調用鏈。 - ClusterBuilderSlot
用於集羣限流、熔斷。 - LogSlot
用於記錄日誌。 - StatisticSlot
用於實時收集實時消息。 - AuthoritySlot
用於權限校驗的。 - SystemSlot
用於驗證系統級別的規則。 - FlowSlot
實現限流機制。 - DegradeSlot
實現熔斷機制。
經過上面的方法,就構建一條 Slot 處理鏈。其實到這裏我們就不難發現,調用 ProcessorSlotChain 的 entry 方法,就是依次調用這些 slot 的方法。關於 ProcessorSlotChain 的類層次結構就不再多說明了,其實現比較簡單,大家如果有興趣的話,可以關注這部分的實現,這裏代表一類場景:一對多、責任鏈的設計模式。
3、Sentinel SphU.entry 處理流程圖
經過上面的探索,我們其實已經找到了 Sentinel 的關於限流、熔斷核心處理邏輯的入口,就是 FlowSlot、DegradeSlot。接下來我們以一張流程圖來結束本文的講解。
本文的目的就是打開 Sentinel 的大門,即尋找實時數據收集、限流、熔斷實現機制的入口,從而正式探尋 Sentienl 的核心實現原理,更多精彩請繼續期待該專欄的後續內容。
點贊是一種美德,如果覺得本文寫的不錯的話,還請幫忙點個贊,您的認可是我持續創造的最大動力,謝謝。
推薦閱讀:源碼分析 Alibaba Sentinel 專欄。
1、Alibaba Sentinel 限流與熔斷初探(技巧篇)
2、源碼分析 Sentinel 之 Dubbo 適配原理
3、源碼分析 Alibaba sentinel 滑動窗口實現原理(文末附原理圖)
作者信息:丁威,《RocketMQ技術內幕》作者,目前擔任中通科技技術平臺部資深架構師,維護 中間件興趣圈公衆號,目前主要發表了源碼閱讀java集合、JUC(java併發包)、Netty、ElasticJob、Mycat、Dubbo、RocketMQ、mybaits等系列源碼。點擊鏈接:加入筆者的知識星球,一起探討高併發、分佈式服務架構,分享閱讀源碼心得。