您好,登錄后才能下訂單哦!
這篇文章主要介紹Alibaba中Sentinel骨架的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
Sentinel 的核心骨架,將不同的 Slot 按照順序串在一起(責任鏈模式),從而將不同的功能(限流、降級、系統保護)組合在一起。slot chain 其實可以分為兩部分:統計數據構建部分(statistic)和判斷部分(rule checking)。核心結構:
// 資源的唯一標識 String resourceName = "testSentinel"; Entry entry = null; String retVal; try { entry = SphU.entry(resourceName, EntryType.IN); // TODO 業務邏輯 retVal = "passed"; } catch (BlockException e) { // TODO 降級邏輯 retVal = "blocked"; } catch (Exception e) { // 異常數統計埋點 Tracer.trace(e); throw new RuntimeException(e); } finally { if (entry != null) { entry.exit(); } }
這段代碼是Sentinel業務埋點示例,通過示例我們可以看出Sentinel對資源的控制入口是SphU.entry(resourceName, EntryType.IN);
,源碼如下:
public static Entry entry(String name, EntryType type) throws BlockException { return Env.sph.entry(name, type, 1, OBJECTS0); }
這里第一個參數是受保護資源的唯一名稱;第二個參數表示流量類型:
EntryType.IN
:是指進入我們系統的入口流量,比如 http 請求或者是其他的 rpc 之類的請求,設置為IN主要是為了保護自己系統。
EntryType.OUT
:是指我們系統調用其他第三方服務的出口流量,設置為OUT是為了保護第三方系統。
這段代碼沒什么邏輯,只是轉發了下,跟進源碼可以發現最終邏輯實在CtSph#entryWithPriority(ResourceWrapper, int, boolean, Object...)
方法中。
Sentinel的核心是資源,這里的資源可以是任何東西,服務,服務里的方法,甚至是一段代碼。而SphU.entry(resourceName);
這段代碼的主要作用是 :
定義一個Sentinel資源
檢驗資源所對應的規則是否生效
核心代碼如下:
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException { // 獲取當前線程上下文,Context是通過ThreadLocal維護,每一個Context都會有一個EntranceNode實例,它是dashboard【簇點鏈路】中的根節點,主要是用來區分調用鏈路的 Context context = ContextUtil.getContext(); if (context instanceof NullContext) { // 如果是 NullContext,表示 Context 個數超過了閾值,這個時候 Sentinel 不會應用規則,即不會觸發限流降級等規則,也不會觸發QPS等數據統計。 // 閾值大小 =Constants.MAX_CONTEXT_NAME_SIZE = 2000,具體可以查看 ContextUtil#trueEnter。 return new CtEntry(resourceWrapper, null, context); } if (context == null) { // 如果沒有設置上下文,即使用默認上下文,默認上下文的名稱是 sentinel_default_context context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME); } if (!Constants.ON) { // Sentinel 的全局控制開關,一旦關閉則不進行任何檢查 return new CtEntry(resourceWrapper, null, context); } // 通過Sentinel的官方文檔我們可以知道,Sentinel的核心功能是基于一系列的功能插槽來實現的,而組織這些功能插槽使用的是責任鏈模式。 // 這里是通過資源(每個資源是唯一的),獲取第一個功能插,即該資源對應的規則入口。 ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); /* * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, * so no rule checking will be done. */ // 如果一個服務中,資源數量操過閾值(最大的插槽鏈),則返回null,即不會再應用規則,直接返回。 // 閾值大小 = Constants.MAX_SLOT_CHAIN_SIZE = 6000 if (chain == null) { return new CtEntry(resourceWrapper, null, context); } // 構建Sentinel調用鏈入口 Entry e = new CtEntry(resourceWrapper, chain, context); try { // 開始執行插槽鏈,如果某個插槽匹配上了某個規則,如限流規則,就會拋出BlockException異常,這時表示請求被拒絕了。 // 業務層面會去捕獲這個異常,然后做熔斷,降級操作。 chain.entry(context, resourceWrapper, null, count, prioritized, args); } catch (BlockException e1) { e.exit(count, args); throw e1; } catch (Throwable e1) { // Sentinel內部異常 RecordLog.info("Sentinel unexpected exception", e1); } return e; }
核心邏輯如下:
通過當前線程的上下文,獲取到當前線程的【簇點鏈路】入口。
判斷全局開關是否關閉。
通過唯一的資源標識獲取到對應的功能插槽鏈(ProcessorSlot)的第一個插槽。
構建Sentinel調用鏈入口,并執行調用鏈
如果拋出BlockException表示觸發了資源限制規則,需要進行熔斷降級。
這里有兩個需要注意的地方:
【簇點鏈路】入口Context的數量是有限制的,最大2000個,通常情況下,我們都不需要顯示設置 context,使用默認的就好了,這樣Context數量限制基本上不會觸發。
SphU.entry(resourceName, EntryType.IN)
,這里的資源的唯一標識resourceName
也是有限制的,最大是6000。當Sentinel與 Servlet 的整合后,CommonFilter
會將所有的對外接口定義成Sentinel的資源,資源名稱就是接口地址,所以要控制好服務接口數量。
ContextUtil#enter(String name, String origin)
的主要作用就是創建當前線程的上下文Context,每個上下文會對應一個EntranceNode(入口節點)實例,通常情況下我們不需要顯示調用該方法。
name
:上下文的唯一標識,也是入口節點的資源名稱。
orgin
:表示來源,通常是服務消費者或調用者的應用名稱,當我們需要對不同來源的消費者或調用者進行限制時就會用到這個參數。
源碼如下:
public static Context enter(String name, String origin) { 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; // 根據上下文名稱獲取入口節點 DefaultNode node = localCacheNameMap.get(name); // 入口節點節點也為空需要新創建入口節點 if (node == null) { // 判斷是否超過最大長度限制(樂觀鎖機制) if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { setNullContext(); return NULL_CONTEXT; } else { try { LOCK.lock(); // 雙重判斷 node = contextNameNodeMap.get(name); if (node == null) { if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { setNullContext(); return NULL_CONTEXT; } else { // 新建入口節點 node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null); // 將入口節點添加到全局根節點下(machine-root) 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); contextHolder.set(context); } return context; }
如果我們再代碼中顯示調用這個方法:
ContextUtil.enter("context1", "service-1"); ... ContextUtil.exit(); ContextUtil.enter("context2", "service-1"); ... ContextUtil.exit();
那么會創建如下一個樹結構圖:
這里有兩點需要注意:
也就是上面說的數量限制,2000。
ContextUtil是通過ThreadLocal來維護當前線程的上下文的,所以當遇到異步線程時需要手動調用
ContextUtil.runOnContext(context, f)
方法來完成父線程和子線程的上下文切換。
文檔中的Demo:
public void someAsync() { try { AsyncEntry entry = SphU.asyncEntry(resourceName); // Asynchronous invocation. doAsync(userId, result -> { // 在異步回調中進行上下文變換,通過 AsyncEntry 的 getAsyncContext 方法獲取異步 Context ContextUtil.runOnContext(entry.getAsyncContext(), () -> { try { // 此處嵌套正常的資源調用. handleResult(result); } finally { entry.exit(); } }); }); } catch (BlockException ex) { // Request blocked. // Handle the exception (e.g. retry or fallback). } }
Sentinel的核心功能是使用的是責任鏈模式實現,lookProcessChain(resourceWrapper)
的主要作用就是用來構造責任鏈,源碼如下:
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) { // 根據資源的唯一標識來做本地緩存 ProcessorSlotChain chain = chainMap.get(resourceWrapper); if (chain == null) { synchronized (LOCK) { chain = chainMap.get(resourceWrapper); if (chain == null) { // 限制資源資對應調用鏈的總數,一個資源對應一條調用鏈 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; }
進一步跟進方法會發現,責任鏈是由SlotChainBuilder#build()````去構建的,默認實現類是
DefaultSlotChainBuilder```,源碼如下:
public class DefaultSlotChainBuilder implements SlotChainBuilder { @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultProcessorSlotChain(); // 找到ProcessorSlot所有的實現類,并排序 List<ProcessorSlot> sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class); for (ProcessorSlot slot : sortedSlotList) { if (!(slot instanceof AbstractLinkedProcessorSlot)) { RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain"); continue; } // 將功能槽放到責任鏈最后 chain.addLast((AbstractLinkedProcessorSlot<?>) slot); } return chain; } }
老版本直接是硬編碼方式:
public class DefaultSlotChainBuilder implements SlotChainBuilder { @Override 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; } }
以下內容來自文檔:
NodeSelectorSlot
: 負責收集資源的路徑,并將這些資源的調用路徑,以樹狀結構存儲起來,用于根據調用路徑來限流降級;
ClusterBuilderSlot
: 則用于存儲資源的統計信息以及調用者信息,例如該資源的 RT, QPS, thread count 等等,這些信息將用作為多維度限流,降級的依據;
StatisticSlot
: 則用于記錄、統計不同緯度的 runtime 指標監控信息;
FlowSlot
: 則用于根據預設的限流規則以及前面 slot 統計的狀態,來進行流量控制;
AuthoritySlot
: 則根據配置的黑白名單和調用來源信息,來做黑白名單控制;
DegradeSlot
: 則通過統計信息以及預設的規則,來做熔斷降級;
SystemSlot
: 則通過系統的狀態,例如 load1 等,來控制總的入口流量;
以上是“Alibaba中Sentinel骨架的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。