通過前面幾篇文章的鋪墊,咱們正式來探討 Sentinel 的 entry 方法的實現流程。即探究進入 Alibaba Sentinel 核心的一把鑰匙。java
@設計模式
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 重載方法,咱們就如下面這個方法爲例來探究其實現原理。緩存
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:咱們先來簡單介紹其核心參數的含義:架構
代碼@2:調用 Env.sph.entry 的方法,其最終會調用 CtSph 的 entry 方法。併發
接下來咱們將重點查看 CtSph 的 entry 方法。app
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 方法。ide
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:咱們先來介紹一下該方法的參數:源碼分析
代碼@2:獲取方法調用的上下文環境,上下環境對象存儲在線程本地變量:ThreadLocal 中,這裏先「劇透」一下,上下文環境中存儲的是整個調用鏈,後續文章會重點介紹。
代碼@3:Sentinel 提供一個全局關閉的開關,若是關閉,返回的 CtEntry 中的 chain 爲空,從這裏能夠看出,若是 chain 爲空,則不會觸發 Sentinel 流控相關的邏輯,從側面也反應了該屬性的重要性。
代碼@4:爲該資源加載處理鏈鏈,這裏是最最重要的方法,將在下文詳細介紹。
代碼@5:根據資源ID、處理器鏈、上下文環境構建 CtEntry 對象。
代碼@6:調用 chain 的 entry 方法。
代碼@7:若是出現 BlockException ,調用 CtEntry 的 exit 方法。
咱們接下來重點看一下 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 方法。
主要有三個實現類,對應熱點、接口網關以及普通場景。咱們接下來將重點介紹 DefaultSlotChainBuilder ,關於熱點限流與網關限流將在後面的文章中詳細探討。
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 的名字基本就能得出其含義。
通過上面的方法,就構建一條 Slot 處理鏈。其實到這裏咱們就不難發現,調用 ProcessorSlotChain 的 entry 方法,就是依次調用這些 slot 的方法。關於 ProcessorSlotChain 的類層次結構就再也不多說明了,其實現比較簡單,你們若是有興趣的話,能夠關注這部分的實現,這裏表明一類場景:一對多、責任鏈的設計模式。
通過上面的探索,咱們其實已經找到了 Sentinel 的關於限流、熔斷核心處理邏輯的入口,就是 FlowSlot、DegradeSlot。接下來咱們以一張流程圖來結束本文的講解。
本文的目的就是打開 Sentinel 的大門,即尋找實時數據收集、限流、熔斷實現機制的入口,從而正式探尋 Sentienl 的核心實現原理,更多精彩請繼續期待該專欄的後續內容。
點贊是一種美德,若是以爲本文寫的不錯的話,還請幫忙點個贊,您的承認是我持續創造的最大動力,謝謝。
推薦閱讀:源碼分析 Alibaba Sentinel 專欄。
一、Alibaba Sentinel 限流與熔斷初探(技巧篇)
二、源碼分析 Sentinel 之 Dubbo 適配原理
三、源碼分析 Alibaba sentinel 滑動窗口實現原理(文末附原理圖)
做者信息:丁威,《RocketMQ技術內幕》做者,目前擔任中通科技技術平臺部資深架構師,維護 中間件興趣圈公衆號,目前主要發表了源碼閱讀java集合、JUC(java併發包)、Netty、ElasticJob、Mycat、Dubbo、RocketMQ、mybaits等系列源碼。點擊連接:加入筆者的知識星球,一塊兒探討高併發、分佈式服務架構,分享閱讀源碼心得。