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

2019-03-18  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读