这篇文章开始分析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();
}
}
运行结果如下:
![](https://img.haomeiwen.com/i3397380/3794a8a985204b12.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,如图:
![](https://img.haomeiwen.com/i3397380/0cfc4c735ecbb2b1.png)
为什么会是这样?
上面提到,所有树的结构类似于栈,后入先出,当最后一个节点exit之后,最后一个节点的前一个节点为最后一个节点,就会存在多个子节点的问题。
再通过代码可以看到,一个 ContextName对应同一个 Resource使用同一个DefaultNode,一个 ContextName对应不同Resource使用同一个EntranceNode
。
网友评论