了解一段sentinel基本代码块
Entry e = null;
try {
e = SphU.entry("resource");
// do something
} catch (BlockException e) {
e.printStackTrace();
} finally {
if(e != null) {
e.exit();
}
}
这是一段sentinal声明资源,并且执行相应逻辑的一段简单代码,其中Entry类型可以理解为入口资源,他的基本属性有
private long createTime; // entry的创建时间
private Node curNode; // 当前的node
/**
* {@link Node} of the specific origin, Usually the origin is the Service Consumer.
*/
private Node originNode; // 来源node
private Throwable error; // 标记异常
protected ResourceWrapper resourceWrapper; // 资源,案例中name=resource
看一下entry对象创建的逻辑,CtSph工具类中
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
// 在同步情况下,context是从线程local中获取,所以不同entry中指向的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,则创建一个默认的context 并且name为default
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);
}
// 狭槽处理器,得到一个链路处理器
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
// 在创建一个CtEntry对象时,同时会更新context对应的curEntry属性值
// 同时更新掉 entry对象中的parent对应值和child对应值
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
// 执行槽链入口方法
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
// 当slot中某一个出现block,说明有规则限制,继续执行,并往上抛异常
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可以手动去创建,包括设置context的name,origin等属性,也可以系统默认创建一个名为default的文本。
resourceWrapper对象很简单,里面只有name和entryType类型,2个属性,在判断resourceWrapper对象是否相同,只要判断name是否相等即可。然后通过resourceWrapper去查找处理槽,得到一个ProcessorSlot对象后。再去创建Entry对象,然后执行了chain的entry方法。如果出现Block异常,执行entry的exit方法,但是继续往上抛异常,如果是其他异常,则记录输入异常日志。
ProcessorSlot是处理槽,在执行entry方法时,考虑一下,在处理一个请求时候,是需要经过加工,经过过滤,经过限制,记录事件次数等等。这么多处理功能,都需要有各种负责的处理槽,可以通过chain链模式一次执行slot。
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
// 一种资源对应一个槽链
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit.
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
chain = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
如何获取一个chain的procSlot,该方法中很重要的是chain对象,由chainMap管理的并且chainMap是静态Map,说明他管理了所有的chain对象。最终又可以得出结果,一个ResourceWapper对象,都有各种对应的chain对象,不会随着线程执行结束而且重新创建,chain对象与声明的resource相关。那么在构建chain链槽对象时,都由哪些ProcessorSlot类型呢?在DefaultSlotChainBuilder中
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new LogSlot());
chain.addLast(new StatisticSlot());
chain.addLast(new SystemSlot());
chain.addLast(new AuthoritySlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());
return chain;
}
通过DefaultProcessorSlotChain对象依次将创建不同ProcessorSlot对象,并且将其放入到链路上,链路设计逻辑其实就是通过start对象,next对象和end对象组合而成的。
@Override
public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {
protocolProcessor.setNext(first.getNext());
first.setNext(protocolProcessor);
if (end == first) {
end = protocolProcessor;
}
}
@Override
public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
end.setNext(protocolProcessor);
end = protocolProcessor;
}
在添加first的时,将protocolProcessor设置next对象为first的next对象,并且first的next对象重新设置当前的protocolProcessor对象。如果说end与first对象一致,那么end对象也要设置成当前的protocolProcessor对象,可以看出 1.first对象是不能变,因为是起始点,2.重置了各种的next对象;
在添加last方法时,设置当前end对象的next对象,并且将end指向新的protocolProcessor对象。
该添加方法只是将重新设置了next对象,那么如何真正执行next对象,在AbstractLinkedProcessorSlot类中
@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);
}
}
@Override
public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
if (next != null) {
next.exit(context, resourceWrapper, count, args);
}
}
void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
throws Throwable {
T t = (T)o;
entry(context, resourceWrapper, t, count, prioritized, args);
}
fireEntry和fireExit方法执行之前,需要执行各种entry和exit方法,当这两个方法真正执行完后,才会执行fire功能的方法,transformEntry方法只是将obj对象进行类型调整。