目录
1.@SentinelResource
对于@SentinelResource注解,相信大家也不会陌生,该注解是在sentinel里面担当起标明该方式受sentinel保护的业务,属于方法级别的注解
@SentinelResource注解的实现是用到了spring的aop技术,在 SentinelResourceAspect类去实现相关的逻辑
2.SentinelResourceAspect
/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
//切点是添加了 @SentinelResource注解的类
@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,调用slot链的相关规则
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
// 执行受保护的方法
return pjp.proceed();
} catch (BlockException ex) {
//执行handleBlockException方法
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) {
//结束退出slot链的调用
entry.exit(1, pjp.getArgs());
}
}
}
}
简单来说,@SentinelResource注解就是一个标记,而Sentinel基于AOP思想,对被标记的方法做环绕增强,完成资源(Entry
)的创建。
进入SphU.entry()
/**
* 记录统计数据并对给定资源执行规则检查。
*
* @param name 受保护资源的唯一名称
* @param trafficType 流量类型(入站、出站或内部)。这是使用
* 标记系统不稳定时是否可以阻塞,
* 只有入站的流量可以被阻塞
* @param resourceType 资源分类 (e.g. Web or RPC)
* @param args 参数流控制或定制槽的参数
*/
public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args)
throws BlockException {
return Env.sph.entryWithType(name, resourceType, trafficType, 1, args);
}
此处的Env会在内部实现Sentinel一些初始化操作,通过SPI机制去加载对应的资源,点击此处可以跳转查看Env的代码解析
进入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 {
//将资源名称等基本信息封装成为一个StringResourceWrapper对象
StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
return entryWithPriority(resource, count, prioritized, args);
}
进入 entryWithPriority 方法
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
//获取到当前线程上下文context
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
// 指示上下文的数量已经超过阈值,这里只初始化条目。不会进行规则检查
return new CtEntry(resourceWrapper, null, context);
}
//如果上下文为空的话,那么创建出一个默认的sentinel_default_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);
}
//获取到processorSlot chain,同一个资源,会创建一个执行链,放入缓存
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
// 表示超出资源(槽链)的数量,所以不会进行规则检查。
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
// 创建 Entry,并将 resource、chain、context 记录在 Entry中
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
// 执行 slotChain
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;
}
该方法的话:
1.获取到当前线程的上下文对象,如果没有上下文对象,如果上下文对象为空,就创建出一个默认的sentinel_default_context上下文对象
2.获取到processorSlotChain,同一个资源,会创建一个执行链,放入缓存
3.执行 slotChain
进入到lookProcessChain(resourceWrapper)
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
//获取当前资源是否有责任链
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
//此处使用了双重检验模式--单例常用的一种
if (chain == null) {
synchronized (LOCK) {
//再次尝试获取是否有值
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// 如果当前的chain长度大于等于6000,就直接不走规则
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
//创建新的责任链数据
chain = SlotChainProvider.newSlotChain();
//此处map的代码感觉很奇怪,觉得原作者是想chainMap.size() + 1扩容,但是忽略了map的扩容机制
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
//将原chainMap指向新的map,从而达到替换
chainMap = newMap;
}
}
}
return chain;
}
此处方法的话,逻辑比较简单:
先尝试从map获取数据,获取不到就采用双重检验模式去操作,会创建出当前资源所需要的SlotChain数据链,然后返回到外面
进入SlotChainProvider.newSlotChain()
public static ProcessorSlotChain newSlotChain() {
//判断是不是非第一次加载,是就走构造方法调用
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
// 利用SPI机制加载位于META-INF/service/com.alibaba.csp.sentinel.slotchain.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();
}
该方法:
1.判断SPI机制是否加载过slotChainBuilder,没有加载过就去加载
2.调用slotChainBuilder.build()去加载对应的规则链
进入slotChainBuilder.build()
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
//利用SPI机制加载位于META-INF/service/com.alibaba.csp.sentinel.slotchain.ProcessorSlot下面的资源
//官方也推荐如果我们想自定义slot也可以放进对应的文件里
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
//遍历把这些slot放进chain里
for (ProcessorSlot slot : sortedSlotList) {
//如果slot没有继承AbstractLinkedProcessorSlot类,那么不能当做slot节点
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
continue;
}
//保存
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
}
这图可以看出chain的first节点就是在上面加载的 DefaultSlotChainBuilder类,因为是第一个放进去的,后面的节点都是next,为什么end节点是DegradeSlot呢?请看下面代码
public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
end.setNext(protocolProcessor);
end = protocolProcessor;
}
总结该方法:
也就简单的通过SPI机制按照顺序加载到slot,然后遍历存放到AbstractLinkedProcessorSlot的next节点,从名字上也可以得知是链式调用。
注:对于slot节点,在官方的wiki也指出
下面开启了在上面获取到chain的slot链式的调用
3.DefaultProcessorSlotChain
进入到DefaultProcessorSlotChain#entry方法:
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
throws Throwable {
//first,也就是责任链的第一个slot
first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
}
这里的first,其实也就是充当一个入口,并没有很多复杂逻辑,其实真正算是first节点的调用可以理解从NodeSelectorSlot节点开始,不过基于责任链的形式,只要记住下一个next节点调用即可
4.NodeSelectorSlot
进入NodeSelectorSlot#entry方法
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
//尝试获取当前资源的DefaultNode
DefaultNode node = map.get(context.getName());
if (node == null) {
synchronized (this) {
node = map.get(context.getName());
if (node == null) {
//如果为空,为当前资源创建一个新的DefaultNode
node = new DefaultNode(resourceWrapper, null);
HashMap<String, DefaultNode> cacheMap