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链执行了。
![](https://img.haomeiwen.com/i28070583/8bec2a3892adf7bc.png)
这个正好就是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,这些维度还是需要我们自己品的。
网友评论