本文继续来分析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系列