美文网首页
Sentinel之Slots插槽源码分析(一)

Sentinel之Slots插槽源码分析(一)

作者: 橘子_好多灰 | 来源:发表于2018-12-23 16:53 被阅读0次

    一、概述

    前面介绍过Sentinel核心框架就是通过插槽链一层层的调用,每个插槽的功能如下:

    • NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结-构存储起来,用于根据调用路径来限流降级。
    • ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据。
    • StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息。
    • LogSlot 则用于记录blockException信息的日志信息,会写入的日志文件中。
    • ParamFlowSlot 则用于根据热点参数进行限流控制的。
    • SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量。
    • AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制。
    • FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制。
    • DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级。

    这边文章会先介绍NodeSelectorSlot和ClusterBuilderSlot功能

    二、节点Node

    我们知道在进行资源的限流降级中,需要拿到Entry,Entry就像是一个凭证,拿到这个凭证,代码才能继续走下去。Entry又被封装到上下文对象Context中。在Context中还有一个entranceNode节点,代表这个资源调用树一个入口。Entry中又有curNode、originNode这两个属性。curNode保存了这一次调用统计信息,riginNode保存了在这个上下文中,所有的调用的统计信息和。

    有这么多Node,那他们之间的关系是怎样的,如图:

    Node
    • Node是一个接口,有很多各种指标的方法,Sentinel就用通过这些指标进行限流降级的。
    • StatisticNode:统计实时统计指标的Node。有两个子类DefaultNode和ClusterNode。
    • EntranceNode:EntranceNode是每个上下文的入口,该节点是挂在root下的,是全局唯一的,每一个context都会对应一个entranceNode。
    • DefaultNode:DefaultNode是记录当前调用实时数据的,在同一个上下文中不同资源关联着不同的DefaultNode。在同一个上下文中,对不同的资源调用,DefaultNode会有子childNode生成。
    • ClusterNode:全局的统计数据,包括 rt, thread count, qps等,相同的资源关DefaultNode联着统一个ClusterNode,无论它在哪个上下文中。

    三、NodeSelectorSlot

    Sentinel之Entry构建源码解析 这篇文章介绍过,Entry可以理解Context这棵树的树干,那么NodeSelectorSlot这个插槽可以理解为正式构建entry叶子而形成的,下面具体分析。

        //注意这里的可以使context的name
        private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
    
      @Override
      public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
            throws Throwable {
            DefaultNode node = map.get(context.getName());
            if (node == null) {
                synchronized (this) {
                    node = map.get(context.getName());
                    if (node == null) {
                        node = Env.nodeBuilder.buildTreeNode(resourceWrapper, null);
                        HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                        cacheMap.putAll(map);
                        cacheMap.put(context.getName(), node);
                        map = cacheMap;
                    }
                    // Build invocation tree
                    ((DefaultNode)context.getLastNode()).addChild(node);
                }
            }
    
            context.setCurNode(node);
      }
    

    分析

    • 1、从map中获取改context下的defaultNode,如果defaultNode不存在,到2,反之直接到4。
    • 2、双重锁校验defaultNode存不存,若不存在,则创建一个defaultNode,并重新构造map,并添加到map中,然后到3。
    • 3、获取context的当前调用Node的LastNode,并把该node添加到子Node中。
    • 4、重新设置Context的curEntry的curNode。

    看图分析
    在一个Context中第一次进入时,在第三步,调用Context的getLastNode方法。

    //在Context中
    public Node getLastNode() {
          //curEntry.getLastNode() 调用CtEntry的方法getLastNode
            if (curEntry != null && curEntry.getLastNode() != null) {
                return curEntry.getLastNode();
            } else {
                return entranceNode;
            }
    }
    

    在CtEntry中

     @Override
        public Node getLastNode() {
            return parent == null ? null : parent.getCurNode();
        }
    

    上篇文章分析过Entry的构建过程,第一次调用时:

    Entry

    可以发现curEntry != null条件满足,但是parent是null。所以,lastNode第一次的值就是context的entranceNode,然后将node添加到entranceNode中,并设置curEntry的curNode节点。然后Context内存结构变成如下。

    defaultNode

    接着又一个请求进入,若资源不同,在生成一个新的Entry后(上一篇分析过)。
    这个时候再次调用context.getLastNode()将会返回parent.getCurNode(),把这个节点放入到lastNode的子节点中,再把node设置给context的curEntry。最后Context内存结构变成如下。

    defaultNode

    这里假设的是资源名不同的情况,若是资源相同的话,则context.getLastNode()).addChild(node);逻辑就不会执行,只会把当前node设置为context的curEntry中。这时的内存结构如图。

    defaultNode

    上面分析了在NodeSelectorSlot中CurEntry叶子构建的过程,在一次构建中会设置entranceNode的childNode。

    资源路径收集

    经过上述分析可以发现,NodeSelectorSlot主要负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。

     ContextUtil.enter("entrance1", "appA");
     Entry nodeA = SphU.entry("nodeA");
     if (nodeA != null) {
        nodeA.exit();
     }
     ContextUtil.exit();
    

    上述代码通过 ContextUtil.enter() 创建了一个名为 entrance1 的上下文,同时指定调用发起者为 appA;接着通过 SphU.entry()请求一个 token,如果该方法顺利执行没有抛 BlockException,表明 token 请求成功。

    以上代码将在内存中生成以下结构:

                  machine-root
                     /     
                    /
             EntranceNode1
                  /
                 /   
          DefaultNode(nodeA
    

    注意:每个 DefaultNode 由资源 ID 和输入名称来标识。换句话说,一个资源 ID 可以有多个不同入口的 DefaultNode。

     ContextUtil.enter("entrance1", "appA");
      Entry nodeA = SphU.entry("nodeA");
      if (nodeA != null) {
        nodeA.exit();
      }
      ContextUtil.exit();
    
      ContextUtil.enter("entrance2", "appA");
      nodeA = SphU.entry("nodeA");
      if (nodeA != null) {
        nodeA.exit();
      }
      ContextUtil.exit();
    

    以上代码将在内存中生成以下结构:

                       machine-root
                        /       \
                       /         \
                 Entrance1     Entrance2
                    /             \
                   /               \
          DefaultNode(nodeA)   DefaultNode(nodeA)
                   |                    |
          +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
    

    可以发现同一个nodeA资源共用一个ClusterNode,而不管它在哪一个上线文中。接下面讲解ClusterBuilderSlot,来看ClusterNode构造。

    四、ClusterBuilderSlot

    ClusterBuilderSlot插槽用于构建资源的 ClusterNode 以及调用来源节点。ClusterNode 保持资源运行统计信息(响应时间、QPS、block 数目、线程数、异常数等)以及原始调用者统计信息列表。来源调用者的名字由 Context.enter(contextName,origin) 中的 origin 标记。

    看源码:

    public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    
        private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap
            = new HashMap<ResourceWrapper, ClusterNode>();
    
        private static final Object lock = new Object();
    
        private ClusterNode clusterNode = null;
    
        @Override
        public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                          boolean prioritized, Object... args)
            throws Throwable {
            if (clusterNode == null) {
                synchronized (lock) {
                    if (clusterNode == null) {
                        // Create the cluster node.
                        clusterNode = Env.nodeBuilder.buildClusterNode();
                        HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<ResourceWrapper, ClusterNode>(16);
                        newMap.putAll(clusterNodeMap);
                        newMap.put(node.getId(), clusterNode);
    
                        clusterNodeMap = newMap;
                    }
                }
            }
            node.setClusterNode(clusterNode);
    
            /*
             * if context origin is set, we should get or create a new {@link Node} of
             * the specific origin.
             */
            if (!"".equals(context.getOrigin())) {
                Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
                context.getCurEntry().setOriginNode(originNode);
            }
    
            fireEntry(context, resourceWrapper, node, count, prioritized, args);
        }
    
    //以下代码省略
    }
    

    细心地人可以发现ClusterBuilderSlot集成的AbstractLinkedProcessorSlot的泛型对象由Object变为DefaultNode了,这是因为DefaultNode已经在NodeSelectorSlot插槽中构建好了。

    来看ClusterBuilderSlot插槽的entry方法做了哪些事。

    • 1、判断clusterNode是否为空,若为空,到2,反之到3。
    • 2、创建一个clusterNode节点,并添加到clusterNodeMap中,注意这里的map的可以使ResourceWrapper,用以表示不区分在哪个上下文中,到3。
    • 3、设置clusterNode到defaultNode中,到4。
    • 4、如果context的origin不为空,则把originNode设置到Context当前cutEntry的originNode中。这个originNode用于后续根据调用源进行限流或资源保护。

    五、我的小结

    1、本文是插槽分析的第一篇,介绍的NodeSelectorSlot和ClusterBuilderSlot的作用。
    2、NodeSelectorSlot用来构建Context的curEntry的叶子节点,不同的资源id在不同的上下文中有不同的入口,并且对应不同的defaultNode,但同一个资源对应同一个clusterNode。
    3、ClusterBuilderSlot设置了defaultNode的clusterNode,并设置了Entry的originNode。clusterNode保存clusterNodeMap中,可以发现系统运行的时间越长,这个map就越稳定。
    4、NodeSelectorSlot和ClusterBuilderSlot是整个插槽链中的前两个插槽,这个两个插槽完成Context数据的封装和对资源调用链Node包装,以便对后续数据的收集和资源的保护限流。


    以上内容,若有不当住处,请指正

    相关文章

      网友评论

          本文标题:Sentinel之Slots插槽源码分析(一)

          本文链接:https://www.haomeiwen.com/subject/eyyxkqtx.html