写在前面

本文继续来分析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种不同维度的流量统计

  1. 资源在上下文维度(DefaultNode)的统计
  2. clusterNode 维度的统计
  3. Origin 来源维度的统计
  4. 入口全局流量的统计

关于流量的统计原理的本文就不深入分析了,接下来的文章中会单独分析

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主要根据预设的资源的统计信息,按照固定的次序,依次生效。如果一个资源对应两条或者多条流控规则,则会根据如下次序依次检验,直到全部通过或者有一个规则生效为止:

并且同样也会根据三种不同的维度来进行限流:

  1. 资源在上下文维度(DefaultNode)的统计
  2. clusterNode 维度的统计
  3. Origin 来源维度的统计

关于流控规则源码的深入分析就不在本篇文章赘述了

DegradeSlot

这个slot主要针对资源的平均响应时间(RT)以及异常比率,来决定资源是否在接下来的时间被自动熔断掉。

总结

  1. 相同的资源({@link ResourceWrapper#equals(Object)})将全局共享相同的{@link ProcessorSlotChain},无论在哪个上下文中
  2. 流控有多个维度,分别包括:1.不同上下文中的资源 2.相同资源 3.入口流量 3.相同的来源

Sentinel系列

Sentinel源码解析二(Slot总览)的更多相关文章

  1. Sentinel源码解析一(流程总览)

    引言 Sentinel作为ali开源的一款轻量级流控框架,主要以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度来帮助用户保护服务的稳定性.相比于Hystrix,Sentinel的设计更加简 ...

  2. Sentinel源码解析四(流控策略和流控效果)

    引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ...

  3. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  4. RxJava2源码解析(二)

    title: RxJava2源码解析(二) categories: 源码解析 tags: 源码解析 rxJava2 前言 本篇主要解析RxJava的线程切换的原理实现 subscribeOn 首先, ...

  5. element-ui 源码解析 二

    Carousel 走马灯源码解析 1. 基本原理:页面切换 页面切换使用的是 transform 2D 转换和 transition 过渡 可以看出是采用内联样式来实现的 举个栗子 <div : ...

  6. iOS即时通讯之CocoaAsyncSocket源码解析二

    原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...

  7. jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究

    终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...

  8. Common.Logging源码解析二

    Common.Logging源码解析一分析了LogManager主入口的整个逻辑,其中第二步生成日志实例工厂类接口分析的很模糊,本随笔将会详细讲解整个日志实例工厂类接口的生成过程! (1).关于如何生 ...

  9. erlang下lists模块sort(排序)方法源码解析(二)

    上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...

随机推荐

  1. php 关于 & 引用赋值

    $a = ; $b = ; echo $a . echo $b . $a = $b; echo $a . echo $b . $a = &$b; echo $a . echo $b . $a ...

  2. SVN部署(Centos7,Ubuntu)

    SVN 简介 SVN是Subversion的简称,是一个开放源代码的版本控制系统,相较于RCS.CVS,它采用了分支管理系统,它的设计目标就是取代CVS.互联网上很多版本控制服务已从CVS迁移到Sub ...

  3. QT 获取可执行程序的路径

    获取到生成.exe目录 QCoreApplication::applicationDirPath(); 获取当前工程目录 QDir::currentPath()

  4. js 函数的多图片懒加载(lazy) 带插件版完整解析

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS实现图片懒加载效果 页面需求 1 ...

  5. HDU 6341 Let Sudoku Rotate

    #include<bits/stdc++.h> using namespace std; #define rep(i,a,b) for(int i=a;i<=b;++i) #defi ...

  6. Math.Round和四舍五入

    Math.Round方法并不是像想象中的四舍五入, 可以从下面的输出结果看出来: Math.Round(3.44, 1); //Returns 3.4. Math.Round(3.45, 1); // ...

  7. .Net Core中使用Dapper构建泛型仓储

    前言:Dapper是.NET的简单对象映射器,在速度方面拥有ORM之王的称号,与使用原始ADO.NET读取数据一样快.ORM是对象关系映射器,它负责数据库和编程语言之间的映射. 仓储主要是用来解耦业务 ...

  8. java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing

    最近在复习java的io流及网络编程.但复习写那些样板程序总是乏味的.便准备写个项目来巩固.想来想去还是聊天项目比较好玩.如果日后完成的比较好自己也可以用(哈哈哈).并且自己后面也要继续巩固java多 ...

  9. python selenium(用例断言)

    1.if ...else ...判断进行断言 from time import * from selenium import webdriver "): driver = webdriver ...

  10. 【Layui__监听button】在form中监听按钮事件

    1. 前言 在使用form表单的按钮时,点击按钮总是页面刷新,代码如下 <button class="layui-btn" lay-submit lay-filter=&qu ...