本文繼續來分析Sentinel的源碼,上篇文章對Sentinel的調用過程作了深刻分析,主要涉及到了兩個概念:插槽鏈和Node節點。那麼接下來咱們就根據插槽鏈的調用關係來依次分析每一個插槽(slot)的源碼。html
默認插槽鏈的調用順序,以及每種類型Node節點的關係都在上面文章開頭分析過 Sentinel源碼解析一java
/** * 相同的資源可是Context不一樣,分別新建 DefaultNode,並以ContextName爲key */ private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10); public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable { // 根據ContextName嘗試獲取DefaultNode DefaultNode node = map.get(context.getName()); if (node == null) { synchronized (this) { node = map.get(context.getName()); if (node == null) { // 初始化DefaultNode,每一個Context對應一個 node = new DefaultNode(resourceWrapper, null); HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size()); cacheMap.putAll(map); cacheMap.put(context.getName(), node); map = cacheMap; } // 構建 Node tree ((DefaultNode)context.getLastNode()).addChild(node); } } context.setCurNode(node); // 喚醒執行下一個插槽 fireEntry(context, resourceWrapper, node, count, prioritized, args); }
NodeSelectorSlot
顧名思義是用來構建Node
的。
咱們能夠看到NodeSelectorSlot
對於不一樣的上下文都會生成一個DefaultNode
。這裏還有一個要注意的點:相同的資源({@link ResourceWrapper#equals(Object)})將全局共享相同的{@link ProcessorSlotChain},不管在哪一個上下文中,所以不一樣的上下文能夠進入到同一個對象的NodeSelectorSlot.entry
方法中,那麼這裏要怎麼區分不一樣的上下文所建立的資源Node呢?顯然可使用上下文名稱做爲映射鍵以區分相同的資源Node。node
而後這裏要考慮另外一個問題。一個資源有可能建立多個DefaultNode
(有多個上下文時),那麼咱們應該如何快速的獲取總的統計數據呢?app
答案就在下一個Slot(ClusterBuilderSlot
)中被解決了。ide
上面有提到一個問題,咱們要如何統計不一樣上下文相同資源的總量數據。ClusterBuilderSlot
給了很好的解決方案:具備相同資源名稱的共享一個ClusterNode
。ui
// 相同的資源共享一個 ClusterNode private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>(); private static final Object lock = new Object(); private volatile ClusterNode clusterNode = null; @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { // 判斷本資源是否已經初始化過clusterNode if (clusterNode == null) { synchronized (lock) { if (clusterNode == null) { // 沒有初始化則初始化clusterNode clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType()); HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16)); newMap.putAll(clusterNodeMap); newMap.put(node.getId(), clusterNode); clusterNodeMap = newMap; } } } // 給相同資源的DefaultNode設置同樣的ClusterNode node.setClusterNode(clusterNode); /* * 若是有來源則新建一個來源Node */ if (!"".equals(context.getOrigin())) { Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin()); context.getCurEntry().setOriginNode(originNode); } fireEntry(context, resourceWrapper, node, count, prioritized, args); }
上面的代碼其實就是作了一件事情,爲資源建立CluserNode
。這裏我又要提一嘴 相同的資源({@link ResourceWrapper#equals(Object)})將全局共享相同的{@link ProcessorSlotChain},不管在哪一個上下文中。也就是說,能進入到同一個ClusterBuilderSlot
對象的entry
方法的請求都是來自同一個資源的,因此這些相同資源須要初始化一個統一的CluserNode
用來作流量的彙總統計。this
代碼比較簡單,邏輯就是打印異常日誌,就不分析了線程
StatisticSlot
是 Sentinel
的核心功能插槽之一,用於統計實時的調用數據。日誌
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { try { // 先進行後續的check,包括規則的check,黑白名單check fireEntry(context, resourceWrapper, node, count, prioritized, args); // 統計默認qps 線程數 node.increaseThreadNum(); node.addPassRequest(count); if (context.getCurEntry().getOriginNode() != null) { // 根據來源統計qps 線程數 context.getCurEntry().getOriginNode().increaseThreadNum(); context.getCurEntry().getOriginNode().addPassRequest(count); } if (resourceWrapper.getEntryType() == EntryType.IN) { // 統計入口 qps 線程數 Constants.ENTRY_NODE.increaseThreadNum(); Constants.ENTRY_NODE.addPassRequest(count); } .... 省略其餘代碼 } }
關於流量的統計原理的本文就不深刻分析了,接下來的文章中會單獨分析code
SystemSlot
比較簡單,其實就是根據StatisticSlot
所統計的全局入口流量進行限流。
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { checkBlackWhiteAuthority(resourceWrapper, context); fireEntry(context, resourceWrapper, node, count, prioritized, args); } void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException { Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules(); if (authorityRules == null) { return; } // 根據資源獲取黑白名單規則 Set<AuthorityRule> rules = authorityRules.get(resource.getName()); if (rules == null) { return; } // 對規則進行校驗,只要有一條不經過 就拋異常 for (AuthorityRule rule : rules) { if (!AuthorityRuleChecker.passCheck(rule, context)) { throw new AuthorityException(context.getOrigin(), rule); } } }
AuthoritySlot
會對資源的黑白名單作檢查,而且只要有一條不經過就拋異常。
@Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { checkFlow(resourceWrapper, context, node, count, prioritized); fireEntry(context, resourceWrapper, node, count, prioritized, args); } void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException { // 檢查限流規則 checker.checkFlow(ruleProvider, resource, context, node, count, prioritized); }
這個slot
主要根據預設的資源的統計信息,按照固定的次序,依次生效。若是一個資源對應兩條或者多條流控規則,則會根據以下次序依次檢驗,直到所有經過或者有一個規則生效爲止:
而且一樣也會根據三種不一樣的維度來進行限流:
關於流控規則源碼的深刻分析就不在本篇文章贅述了
這個slot
主要針對資源的平均響應時間(RT)以及異常比率,來決定資源是否在接下來的時間被自動熔斷掉。
Sentinel系列