NodeSelectorSlotClusterBuilder
1. NodeSelectorSlot
第一个slot 是 NodeSelectorSlot开始,这个NodeSelectorSlot从命名上就能猜出来,做node选择的slot。看slot的源码,我们要时刻记着,一个资源对应一个slot链。这里我们先看下~NodeSelectorSlot·它的成员变量
/** * {@link DefaultNode}s of the same resource in different context. */ private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
就是个map,存储着资源与node的对应关系
1.1 entry方法
@Override public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable { // 一个context name 对应一个defaultNode 对象 // 从缓存中获取DefaultNode DefaultNode node = map.get(context.getName()); // DCL if (node == null) { synchronized (this) { node = map.get(context.getName()); if (node == null) { // 创建一个DefaultNode,并放入缓存map node = new DefaultNode(resourceWrapper, null); HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size()); cacheMap.putAll(map); cacheMap.put(context.getName(), node); map = cacheMap; // Build invocation tree // 将新建的node添加到调用树中 ((DefaultNode) context.getLastNode()).addChild(node); } } } // 设置当前node context.setCurNode(node); // todo 触发下一个节点 fireEntry(context, resourceWrapper, node, count, prioritized, args); }
其实不关心业务含义的话,看这段代码很简单,无非就是从map中获取DefaultNode对象,如果不存在的话就创建。 但是我们不知道这个context是什么东西,它的name属性又是什么东西,有经验的程序员同学,一看到这个context就知道是个上下文,而且一般配合着ThreadLocal来使用,代码中倒数第二句把node设置到了这个context中,这个node就会跟着这个context一直往下传,但是我们知道这些是远远不够的,这时候我们就要结合之前的一些代码来看了:查看中4.3案例:
ContextUtil.enter(target, origin); entry = SphU.entry(target, EntryType.IN);
这里解释下target跟origin,target的话就是当前资源,这里也就是你请求路径,origin,这个是上游的请求路径,在微服务中,上游服务调用下游服务,sentinel会通过一些手段将上游的资源信息带到下游服务中,这个不用太关心。 先看下这个ContextUtil.enter(target, origin);这段代码
protected static Context trueEnter(String name, String origin) { // 尝试从ThreadLocal中获取context Context context = contextHolder.get(); // 若ThreadLocal中没有,则尝试从缓存map中获取 if (context == null) { // 缓存map的key为context名称,value为EntranceNode Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap; // DCL 双重检测锁,防止并发创建对象 DefaultNode node = localCacheNameMap.get(name); if (node == null) { // 若缓存map的size 大于 context数量的最大阈值,则直接返回NULL_CONTEXT if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { setNullContext(); return NULL_CONTEXT; } else { LOCK.lock(); try { node = contextNameNodeMap.get(name); if (node == null) { if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { setNullContext(); return NULL_CONTEXT; } else { // 创建一个EntranceNode node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null); // Add entrance node. // 将新建的node添加到Root Constants.ROOT.addChild(node); // 将新建的node写入到缓存map // 为了防止"迭代稳定性问题"-iterate stable 对于共享集合的写操作 Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1); newMap.putAll(contextNameNodeMap); newMap.put(name, node); contextNameNodeMap = newMap; } } } finally { LOCK.unlock(); } } } // 将context的name与entranceNode 封装成context context = new Context(node, name); // 初始化context的来源 context.setOrigin(origin); // 将context写入到ThreadLocal contextHolder.set(context); }
别看很长,难度不大这段代码,首先是从contextHolder获取这个context,这个contextHolder就是个ThreadLocal ThreadLocal<Context> contextHolder = new ThreadLocal<>(); 这里想想,如果是项目刚启动,还没有请求过来,肯定是没有的,这个时候就会继续往下走,从一个缓存map中获取DefaultNode ,这个也是没有的,接着就是判断map的大小,不能超过2000,超过了就返回一个null context,不超过的话就会创建EntranceNode 对象,将这个资源传入构造,同时将新创建的EntranceNode 对象挂在ROOT 节点上,缓存到这个map中。这个ROOT节点其实也是个EntranceNode,不过它的资源名字是machine-root。接着往下看就是创建context了,然后将创建的context放到ThreadLocal中。
好了,到这里ContextUtil.enter(target, origin);这句代码就分析完成了。
接着分析下entry = SphU.entry(target, EntryType.IN);
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException { // 获取某个线程对应的context Context context = ContextUtil.getContext(); if (context instanceof NullContext) { // The {@link NullContext} indicates that the amount of context has exceeded the threshold, // so here init the entry only. No rule checking will be done. return new CtEntry(resourceWrapper, null, context); } // 如果是null的话 if (context == null) { // Using default context. context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType()); } // Global switch is close, no rule checking will do. if (!Constants.ON) { return new CtEntry(resourceWrapper, null, context); } ///创建 processor Slot ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); /* * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, * so no rule checking will be done. */ if (chain == null) { return new CtEntry(resourceWrapper, null, context); } // 创建ctEntry对象 Entry e = new CtEntry(resourceWrapper, chain, context); try { //chain entry chain.entry(context, resourceWrapper, null, count, prioritized, args); } catch (BlockException e1) { e.exit(count, args); throw e1; } catch (Throwable e1) { // This should not happen, unless there are errors existing in Sentinel internal. RecordLog.info("Sentinel unexpected exception", e1); } return e; }
-
先获取context,上面我们创建了context,这里直接获取就OK了。
-
接着构建slot链,这里我们就不看了,之前都介绍过。
-
接着创建CtEntry对象,这里没啥好看的
-
最后就是进入slot链执行了。

这个正好就是NodeSelectorSlot 类上面注释画的那个关系。
- ROOT(machine-root) 是顶级的节点,也就是根节点
- EntranceNodeOne是一个线程节点,属于context,同理EntranceNodeTwo
- 而NodeSelectorSlot 里面维护的那个map,是相同资源不同context DefaultNode
1.2 exit
@Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { // 没干啥操作,接着往后调用 fireExit(context, resourceWrapper, count, args); }
1.3 总结
本文是第一个slot NodeSelectorSlot的源码解析,也是稍微简单的,代码不难,但是这里面Node的关系还是需要慢慢的体会
2. ClusterBuilderSlot
在第1小节中我们分析了第一个slot NodeSelectorSlot的源码,这个slot主要是为相同资源不同的线程创建node,缓存没有创建,然后塞到当前线程中。本小节分析的ClusterBuilderSlot 从名字上看也能知道是与集群有关的,它主要是为某个资源创建集群node的,也就是每个资源就一个集群node,代码也很简单,我们看下。
首先看下ClusterBuilderSlot的成员
private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();//资源 ---》clusterNode private static final Object lock = new Object(); private volatile ClusterNode clusterNode = null;// clusterNode
可以看到这个clusterNodeMap 其实就是个缓存 资源与 ClusterNode 对应关系的,一个资源对应着一个ClusterNode 对象。lock就是个锁,这个下面会用到,一个资源一把锁。clusterNode这个是对象成员,一个资源对应一个clusterNode。
2.1 entry方法
@Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { if (clusterNode == null) { synchronized (lock) { if (clusterNode == null) { // Create the cluster node. // 创建 cluster node clusterNode = new ClusterNode(); HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16)); newMap.putAll(clusterNodeMap); ///node.getId() 这个其实是resource newMap.put(node.getId(), clusterNode); clusterNodeMap = newMap; } } } /// 设置clusterNode node.setClusterNode(clusterNode); /* * if context origin is set, we should get or create a new {@link Node} of * the specific origin. * 此上下文的起源(通常表示不同的调用者,例如服务使用者名称或起源IP)。 */ if (!"".equals(context.getOrigin())) { //创建originNode 然后在当前entry 甚至originNode Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin()); context.getCurEntry().setOriginNode(originNode); } fireEntry(context, resourceWrapper, node, count, prioritized, args); }
整体步骤如下:
- 就是这个资源对应的clusterNode不存在的话,就创建一个ClusterNode实例,然后放到map中缓存
- 再就是设置到往当前node中,这个node是上个NodeSelectorSlot 创建的那个。
- 再就是从context取出origin,这个origin得解释下,其实就是上层资源,比如说微服务中,上层服务调用下层服务,sentinel会通过一些手段将上层服务资源传到下层服务,然后就能获取到这个origin,如果我们这个服务不是上层服务的话,这个origin一般会有的,这里就是获取origin的node,他也有个map专门缓存的,如果没有就创建。- 最后将这个origin node塞到当前调用entry中。
- 最后就是调用下一个slot。
2.2 exit方法
这个与NodeSelectorSlot 一样,啥事没干
@Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); }
2.3 总结
这个ClusterBuilderSlot 还是很简单,就是为资源创建一个clusternode ,然后塞到当前调用node中,同时将origin node 塞到当前调用entry中。其实从ClusterBuilderSlot 与NodeSelectorSlot 中我们会发现,开始它就是准备各个维度统计使用的node,这些维度还是需要我们自己品的。
#Java##程序员#
查看13道真题和解析