Sentinel中两种节点插槽的原理

在sentinel中,节点用于资源统计,有两种节点类型,默认节点(DefaultNode)和集群节点(ClusterNode),而入口节点(EntranceNode)是默认节点的子类型。入口节点是在创建上下文的时候创建的,而在插槽链的执行过程中,会创建默认节点和集群节点。


这两种插槽的逻辑很相似,而且比较简单,所以合在一起来介绍。

NodeSelectorSlot

这个插槽用来选择节点,用于资源统计。

v2.0.0
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来调用下一个插槽。

v2.0.0
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

v2.0.0
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);
}

这个插槽负责创建集群节点,用于集群资源统计。