Sentinel中两种节点插槽的原理
在sentinel中,节点用于资源统计,有两种节点类型,默认节点(DefaultNode)和集群节点(ClusterNode),而入口节点(EntranceNode)是默认节点的子类型。入口节点是在创建上下文的时候创建的,而在插槽链的执行过程中,会创建默认节点和集群节点。
这两种插槽的逻辑很相似,而且比较简单,所以合在一起来介绍。
NodeSelectorSlot
这个插槽用来选择节点,用于资源统计。
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
/*
* 根据context名称来获取defaultNode,之所以没有使用resource的名称?
* 可以从不同的入口或者说是context进入到某个node,而不同context有不同的slot chain
* 但一个resource,可以有多个context,也就是对应多个defaultNode
* (参考ContextUtil中创建context相关的逻辑,当时创建的是EntranceNode类型的入口节点,继承了DefaultNode),
* 所以要用context的名称
* (认真读懂上面的英文注释)
*/
DefaultNode node = map.get(context.getName());
// 如果为null,则新建一个
if (node == null) {
synchronized (this) { // DCL实现
// 再次尝试获取
node = map.get(context.getName());
if (node == null) {
// 创建默认节点对象
node = new DefaultNode(resourceWrapper, null);
// 新建一个map,并添加新旧数据,然后赋值给map属性
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的curEntry属性的curNode属性中
context.setCurNode(node);
// 调用chain中的下一个slot:ClusterBuilderSlot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
根据上下文来查找DefaultNode对象,没找到的话就新创建一个。最后调用fireEntry来调用下一个插槽。
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java
public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
private AbstractLinkedProcessorSlot<?> next = null;
@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
if (next != null) {
// 调用下一个插槽
next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
}
}
@SuppressWarnings("unchecked")
void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
throws Throwable {
T t = (T)o;
// 调用子类实现的entry方法
entry(context, resourceWrapper, t, count, prioritized, args);
}
@Override
public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
if (next != null) {
next.exit(context, resourceWrapper, count, args);
}
}
public AbstractLinkedProcessorSlot<?> getNext() {
return next;
}
public void setNext(AbstractLinkedProcessorSlot<?> next) {
this.next = next;
}
}
ClusterBuilderSlot
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java
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为空,则新建一个,用了DCL来提高性能(避免了尝试获取锁)
if (clusterNode == null) {
synchronized (lock) {
if (clusterNode == null) {
// Create the cluster node.
// 创建节点对象
clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
// COW实现
HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
newMap.putAll(clusterNodeMap);
newMap.put(node.getId(), clusterNode);
clusterNodeMap = newMap;
}
}
}
// 将clusterNode设置到传入进来的defaultNode中
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);
}
// 调用下一个slot,下一个来分析StatisticSlot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
这个插槽负责创建集群节点,用于集群资源统计。