美文网首页
【sentinel】深入浅出之原理篇NodeSelectorSl

【sentinel】深入浅出之原理篇NodeSelectorSl

作者: 一滴水的坚持 | 来源:发表于2019-03-18 16:36 被阅读0次

    这篇文章开始分析SlotChain中的各个Slot,第一个Slot为NodeSelectorSlot
    NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;

    public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
       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 {
           //从上下文获取Context命名的Node节点
           DefaultNode node = map.get(context.getName());
           if (node == null) {
               synchronized (this) {
                   node = map.get(context.getName());
                   if (node == null) {
                       //创建resource对应的Node 类型为DeaultNode
                       node = Env.nodeBuilder.buildTreeNode(resourceWrapper, null);
                       //保存Node  一个resource对应一个Node
                       HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                       cacheMap.putAll(map);
                       cacheMap.put(context.getName(), node);
                       map = cacheMap;
                   }
                   // 添加到Context的Node节点,这里构造了一棵节点🌲
                   ((DefaultNode)context.getLastNode()).addChild(node);
               }
           }
           //设置当前Context的当前节点为node
           context.setCurNode(node);
           //调用下一个Slot
           fireEntry(context, resourceWrapper, node, count, prioritized, args);
       }
    
       @Override
       public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
           fireExit(context, resourceWrapper, count, args);
       }
    }
    

    可以看到,在NodeSelectorSlot节点中,首先根据ContextName获取默认节点,若默认节点不存在,则创建一个默认节点,并将改节点保存在缓存map中,然后设置当前节点为默认节点

    调用顺序资源的调用路径树形结构哪里存储?

    重点就在于下面这行代码

     ((DefaultNode)context.getLastNode()).addChild(node);
    

    通过上下文首先获取最后一个节点,如果当前节点不为null,则当前节点就为最后一个节点,如果当前节点为null,则为entranceNode。
    获取到最后一个节点,然后将当前节点挂在最后一个节点后面。

    public class Context {
        private final String name;
        private DefaultNode entranceNode;
        private Entry curEntry;
        private String origin = "";
        private final boolean async;
    
        public Node getLastNode() {
            //从当前节点获取
            if (curEntry != null && curEntry.getLastNode() != null) {
                return curEntry.getLastNode();
            } else {
                //直接返回entranceNode
                return entranceNode;
            }
        }
    }
    
    public class DefaultNode extends StatisticNode {
    
        private ResourceWrapper id;
        private volatile Set<Node> childList = new HashSet<>();
        private ClusterNode clusterNode;
    
        public void addChild(Node node) {
            if (node == null) {
                RecordLog.warn("Trying to add null child to node <{0}>, ignored", id.getName());
                return;
            }
            //如果子节点包含了该节点,则跳过
            if (!childList.contains(node)) {
                synchronized (this) {
                    //不包含该节点,则将当前节点挂在子节点里面
                    if (!childList.contains(node)) {
                        Set<Node> newSet = new HashSet<>(childList.size() + 1);
                        newSet.addAll(childList);
                        newSet.add(node);
                        childList = newSet;
                    }
                }
                RecordLog.info("Add child <{0}> to node <{1}>", ((DefaultNode)node).id.getName(), id.getName());
            }
        }
    }
    

    这种结构有点类似大学学的数据结构:
    先来的位于栈首,后来的位于栈尾,后进先出。栈首位置永远是EntranceNode节点
    举个例子:创建一个Context和5个resourceEntry,看一下整个树的结构会是什么样子。

     public static void main(String[] args) {
        try {
            Context context=ContextUtil.enter("context1");
            Entry entry=SphU.entry("A");
            Entry entry2=SphU.entry("B");
            Entry entry3=SphU.entry("C");
            Entry entry4=SphU.entry("D");
            Entry entry5=SphU.entry(""E);
            entry.exit();
            entry2.exit();
            entry3.exit();
            entry4.exit();
            entry5.exit();
            ContextUtil.exit();
        } catch (BlockException ex) {
            // 处理被流控的逻辑
            System.out.println("blocked!");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    

    运行结果如下:


    运行结果.png

    可以看到,EntranceNode的子节点为A,A的子节点为B,B的子节点为C,一直到E。

    既然子Node都是一个,那么为什么还需要HashSet来保存,而不是定义一个DefaultNode?

    public static void main(String[] args) {
    
        try {
            Context context=ContextUtil.enter("context1");
            Entry entry=SphU.entry("A");
            Entry entry2=SphU.entry("B");
            entry2.exit();
            Entry entry3=SphU.entry("C");
            entry3.exit();
            Entry entry4=SphU.entry("D");
            Entry entry5=SphU.entry("E");
            entry.exit();
    /*                entry2.exit();
            entry3.exit();*/
            entry4.exit();
            entry5.exit();
            ContextUtil.exit();
        } catch (BlockException ex) {
            // 处理被流控的逻辑
            System.out.println("blocked!");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    

    运行上述代码,你就会发现,节点A的子Node有三个,B,C,D,节点D的子节点有一个E,如图:


    运行结果

    为什么会是这样?

    上面提到,所有树的结构类似于栈,后入先出,当最后一个节点exit之后,最后一个节点的前一个节点为最后一个节点,就会存在多个子节点的问题。

    再通过代码可以看到,一个 ContextName对应同一个 Resource使用同一个DefaultNode,一个 ContextName对应不同Resource使用同一个EntranceNode

    相关文章

      网友评论

          本文标题:【sentinel】深入浅出之原理篇NodeSelectorSl

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