Sentinel资源入口
在Sentinel中,一般会在调用目标资源之前先调用SphU类的entry方法来创建资源入口,sentinel的功能都被封装在里面,本文就来分析一下这里面到底做了什么事情。
SentinelResourceAspect
先以这个切面类为例来分析一下资源入口是怎么创建的。
java
sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/SentinelResourceAspect.java
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
// 获取目标方法
Method originMethod = resolveMethod(pjp);
// 获取方法上的@SentinelResource注解
SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
if (annotation == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelResource annotation");
}
// 获取资源名称
String resourceName = getResourceName(annotation.value(), originMethod);
// 获取入口类型
EntryType entryType = annotation.entryType();
// 获取资源类型
int resourceType = annotation.resourceType();
Entry entry = null;
try {
// 创建入口
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
// 调用目标方法
return pjp.proceed();
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
throw ex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
return handleFallback(pjp, annotation, ex);
}
// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
// 退出入口
entry.exit(1, pjp.getArgs());
}
}
}
}
SphU
entry_12
Env
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java
public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args)
throws BlockException {
// 申请的资源数量传入的是1
return Env.sph.entryWithType(name, resourceType, trafficType, 1, args);
}
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/Env.java
public class Env {
public static final Sph sph = new CtSph();
static {
// If init fails, the process will exit.
InitExecutor.doInit();
}
}
该类中有很多entry相关方法,都委托给Env.sph来执行,所以SphU类可以看作是CtSph类的门面类。
实在是想吐槽一下这里的类名称用简写,根本不知道是什么意思,找了半天资料也没有明确的说法。
CtSph
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
@Override
public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args)
throws BlockException {
return entryWithType(name, resourceType, entryType, count, false, args);
}
@Override
public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
Object[] args) throws BlockException {
// 创建wrapper对象,封装了资源的属性
StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
return entryWithPriority(resource, count, prioritized, args);
}
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
// 获取当前线程的上下文
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);
}
// 如果还不存在context,那么新建一个
if (context == null) {
// Using default context.
// 这里默认创建的context的名称为 sentinel_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);
}
// 获取资源对应的处理器插槽链,如果插槽数量超过了最大值,那么会返回null
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,异步环境下会创建AsyncEntry,Entry抽象类就这两个实现类,
* 传入context,会把创建好的entry对象设置到context的curEntry属性中
*/
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
// 进入插槽责任链并开始执行
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;
}
在entryWithType方法中,将资源属性封装为了wrapper对象。然后在entryWithPriority方法中创建了Context(如果不存在的话),查找或创建并执行处理器插槽链。
上下文对象
获取上下文对象
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java
private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
public static Context getContext() {
return contextHolder.get();
}
从这里可以看出来,上下文对象是存放在线程本地存储中的。
创建上下文对象
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
private final static class InternalContextUtil extends ContextUtil {
static Context internalEnter(String name) {
return trueEnter(name, "");
}
static Context internalEnter(String name, String origin) {
return trueEnter(name, origin);
}
}
调用了父类ContextUtil中的trueEnter方法。
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java
protected static Context trueEnter(String name, String origin) {
Context context = contextHolder.get();
if (context == null) {
Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
/*
* 对于context不存在的情况需要新建context,这种情况传入的name就是Constants.CONTEXT_DEFAULT_NAME
* 而类加载时会执行initDefaultContext方法,就已经创建了entrance节点了,所以这里获取的node应该不会是null
*/
DefaultNode node = localCacheNameMap.get(name);
if (node == null) {
// 判断contextNameNodeMap的size是不是超过了2000
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 {
// 这里的逻辑就相当于是initDefaultContext方法
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
// Add entrance node.
Constants.ROOT.addChild(node);
Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
newMap.putAll(contextNameNodeMap);
newMap.put(name, node);
contextNameNodeMap = newMap;
}
}
} finally {
LOCK.unlock();
}
}
}
// 创建Context对象
context = new Context(node, name);
context.setOrigin(origin);
// 将context保存到ThreadLocal中
contextHolder.set(context);
}
return context;
}
这里除了创建上下文对象以外,还创建了入口节点EntranceNode。另外对上下文对象和入口节点对象的个数做了限制,上限为2000个。
处理器插槽链
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
= new HashMap<ResourceWrapper, ProcessorSlotChain>();
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
// 从缓存中获取插槽链
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
// 再次尝试获取,DCL实现
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit.
// 判断插槽的数量是否超过最大值,默认是6000
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
// 初始化新的插槽链
chain = SlotChainProvider.newSlotChain();
// 创建一个新的map,然后填充新旧数据,并赋值给chainMap属性,典型的COW操作
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
如果还没有创建过过处理器插槽链,则新建一个,同样做了限制,最多只能有6000个处理器插槽链。
创建处理器插槽链
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java
public static ProcessorSlotChain newSlotChain() {
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
// Resolve the slot chain builder SPI.
// 通过SPI机制获取SlotChainBuilder实现,默认是DefaultSlotChainBuilder
slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
if (slotChainBuilder == null) {
// Should not go through here.
RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
slotChainBuilder = new DefaultSlotChainBuilder();
} else {
RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
slotChainBuilder.getClass().getCanonicalName());
}
// 构建插槽链
return slotChainBuilder.build();
}
这里利用了SPI机制来加载SlotChainBuilder的实现类,在sentinel-core模块的META-INF.service目录下配置了该类型的实现类。
txt
# Default slot chain builder
com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
下面就来看看是怎么构建的。
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java
@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
// 创建插槽链对象
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
/*
* 通过SPI机制加载ProcessorSlot实现,并排序
* 参考sentinel-core模块的resources/META-INF.services目录下面的配置文件,默认配置了一些类
*/
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
for (ProcessorSlot slot : sortedSlotList) {
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
continue;
}
/*
* 将一个个slot加载chain的末尾,串联成为一条slot链条
* 整个链条结构可参考图片链接:https://upload-images.jianshu.io/upload_images/3397380-10649f456ff62747.png?imageMogr2/auto-orient/strip|imageView2/2/w/712/format/webp
* chain的first属性指向的是NodeSelector,该类是链条的入口,所以首先分析该类
* 第二个是ClusterBuilderSlot,这两个比较固定
* */
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
}
这里再次用SPI机制来加载ProcessorSlot的实现类,同样是在sentinel-core模块下配置了实现类。
txt
# Sentinel default ProcessorSlots
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
com.alibaba.csp.sentinel.slots.logger.LogSlot
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
com.alibaba.csp.sentinel.slots.system.SystemSlot
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot
com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot
然后将加载到的这些插槽对象添加到插槽链中。下面是插槽链的执行流程图。