寫在前面
本文繼續來分析Sentinel的源碼,上篇文章對Sentinel的調用過程做了深入分析,主要涉及到了兩個概念:插槽鏈和Node節點。那么接下來我們就根據插槽鏈的調用關系來依次分析每個插槽(slot)的源碼。
默認插槽鏈的調用順序,以及每種類型Node節點的關系都在上面文章開頭分析過 Sentinel源碼解析一
NodeSelectorSlot
/**
* 相同的資源但是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。
然后這里要考慮另一個問題。一個資源有可能創建多個DefaultNode
(有多個上下文時),那么我們應該如何快速的獲取總的統計數據呢?
答案就在下一個Slot(ClusterBuilderSlot
)中被解決了。
ClusterBuilderSlot
上面有提到一個問題,我們要如何統計不同上下文相同資源的總量數據。ClusterBuilderSlot
給了很好的解決方案:具有相同資源名稱的共享一個ClusterNode
。
// 相同的資源共享一個 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
用來做流量的匯總統計。
LogSlot
代碼比較簡單,邏輯就是打印異常日志,就不分析了
StatisticSlot
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);
}
.... 省略其他代碼
}
}
StatisticSlot主要做了4種不同維度的流量統計
- 資源在上下文維度(DefaultNode)的統計
- clusterNode 維度的統計
- Origin 來源維度的統計
- 入口全局流量的統計
關於流量的統計原理的本文就不深入分析了,接下來的文章中會單獨分析
SystemSlot
SystemSlot
比較簡單,其實就是根據StatisticSlot
所統計的全局入口流量進行限流。
AuthoritySlot
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
會對資源的黑白名單做檢查,並且只要有一條不通過就拋異常。
FlowSlot
@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
主要根據預設的資源的統計信息,按照固定的次序,依次生效。如果一個資源對應兩條或者多條流控規則,則會根據如下次序依次檢驗,直到全部通過或者有一個規則生效為止:
並且同樣也會根據三種不同的維度來進行限流:
- 資源在上下文維度(DefaultNode)的統計
- clusterNode 維度的統計
- Origin 來源維度的統計
關於流控規則源碼的深入分析就不在本篇文章贅述了
DegradeSlot
這個slot
主要針對資源的平均響應時間(RT)以及異常比率,來決定資源是否在接下來的時間被自動熔斷掉。
總結
- 相同的資源({@link ResourceWrapper#equals(Object)})將全局共享相同的{@link ProcessorSlotChain},無論在哪個上下文中
- 流控有多個維度,分別包括:1.不同上下文中的資源 2.相同資源 3.入口流量 3.相同的來源
Sentinel系列