sentinel是一个轻量级高可用流量控制组件,使用sentinel来进行流量控制的简单demo如下:
Entry entry = null;
try {
entry = SphU.entry("abc");
entry = SphU.entry("def");
} catch (BlockException e1) {
} finally {
if (entry != null) {
entry.exit();
}
}
将字符串“abc”抽象成一个资源,然后调用接口的时候进行限制。进入SphU.entry分析一下。
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
return Env.sph.entry(name, type, count, args);
}
public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
return entryWithPriority(resourceWrapper, count, false, args);
}
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);
}
if (context == null) {
// Using default context.
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
// Global switch is close, no rule checking will do.
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}
// 获取该资源对应的SlotChain
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);
}
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
// 执行Slot的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;
}
在函数 ContextUtil.getContext()中获取上下文对象context
1)首先在ThreadLocal获取,获取不到就创建,不然就返回
2)然后再Map中根据ContextName找一个Node
3)没有找到Node就加锁的方式,创建一个EntranceNode,然后放入Map中
4)创建Context,设置node,name,origin,再放入ThreadLocal中
Context的结构如下
在函数entryWithPriority中,获取Context后就创建调用链slot chain。
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
然后创建CtEntry
Entry e = new CtEntry(resourceWrapper, chain, context);
Entry e = new CtEntry(resourceWrapper, chain, context);
class CtEntry extends Entry {
protected Entry parent = null;
protected Entry child = null;
protected ProcessorSlot<Object> chain;
protected Context context;
protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
super(resourceWrapper);
this.chain = chain;
this.context = context;
setUpEntryFor(context);
}
private void setUpEntryFor(Context context) {
// The entry should not be associated to NullContext.
if (context instanceof NullContext) {
return;
}
// 首次返回为null
this.parent = context.getCurEntry();
if (parent != null) {
((CtEntry) parent).child = this;
}
// 设置Context的curEntry为当前的CtEntry
context.setCurEntry(this);
}
}
在CtEntry中设置context的curEntry,Context的结构就变成下面这样
调用函数chain.entry,第一次执行NodeSelectorSlot方法
@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
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 {
DefaultNode node = map.get(context.getName());
if (node == null) {
synchronized (this) {
node = map.get(context.getName());
if (node == null) {
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;
// 构建调用树,添加entranceNode的子节点
((DefaultNode) context.getLastNode()).addChild(node);
}
}
}
// 设置Context的当前节点
context.setCurNode(node);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
}
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 {
return entranceNode;
}
}
public Context setCurNode(Node node) {
this.curEntry.setCurNode(node);
return this;
}
}
当第二次调用entry = SphU.entry("abc");第二次执行NodeSelectorSlot时
因为资源名不同所以对应的职责链条,因此NodeSelectorSlot会重新创建DefaultNode。
通过上面的代码分析,可以看出context的数据结构的演变如下:
1、创建context
刚开始初始化的时候,context中的curEntry属性是没有值的,如下图所示:
2、然后创建Entry
每创建一个新的Entry对象时,都会重新设置context的curEntry,并将context原来的curEntry设置为该新Entry对象的父节点,如下图所示:
3、退出Entry
某个Entry退出时,将会重新设置context的curEntry,当该Entry是最顶层的一个入口时,将会把ThreadLocal中保存的context也清除掉,如下图所示: