一、流控的主流程
1、流控的入口
从上一篇《Spring Cloud Alibaba-Sentinel源码阅读(一)-Sentinel的使用》中可知,Sentinel流控的入口是SphU#entry方法,而SphU类据说是semaphore 信号量的缩写,这个SphU#entry方法的大概就是进入流控了,如果方法抛BlockException,则代表被流控了,如果正常返回一个Entry则正常放行。
SphU#entry(java.lang.String, int, com.alibaba.csp.sentinel.EntryType, java.lang.Object[])
-> SphResourceTypeSupport#entryWithType(java.lang.String, int, com.alibaba.csp.sentinel.EntryType, int, java.lang.Object[])
-> CtSph#entryWithType(java.lang.String, int, com.alibaba.csp.sentinel.EntryType, int, java.lang.Object[])
-> CtSph#entryWithPriority(com.alibaba.csp.sentinel.slotchain.ResourceWrapper, int, boolean, java.lang.Object...)
CtSph#entryWithPriority
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
// 获取方法调用的上下文环境,上下环境对象存储在线程本地变量:ThreadLocal 中,上下文环境中存储的是整个调用链,
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);
}
// 全局关闭的开关,如果关闭,返回的 CtEntry 中的 chain 为空,如果 chain 为空,则不会触发 Sentinel 流控相关的逻辑
// Global switch is close, no rule checking will do.
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}
// 为资源加载处理链链,这里是最最重要的方法
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 {
// 调用 chain 的 entry 方法。
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
// 如果出现 BlockException ,调用 CtEntry 的 exit 方法。
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;
}
说下上面方法的几个入参:
- ResourceWrapper resourceWrapper 资源的包装类型,可以是字符串类型的资源描述,也可以是方法类的。
- int count 此次需要消耗的令牌,正常是1
- boolean prioritized 是否有优先级。
CtSph#lookProcessChain
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
// chainMap 一个全局的缓存表,即同一个资源 ResourceWrapper (同一个资源名称) 会共同使用同一个 ProcessorSlotChain ,
// 不同的线程在访问同一个资源保护的代码时,这些线程将共用 ProcessorSlotChain 中的各个 ProcessorSlot
// 注意留意 ResourceWrapper 的 equals 方法与 hashCode 方法。
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;
}
// 通过 SlotChainProvider 创建对应的处理链
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;
}
SlotChainProvider#newSlotChain
public static ProcessorSlotChain newSlotChain() {
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
// 通过SPI机制,默认是DefaultSlotChainBuilder
// Resolve the slot chain builder SPI.
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();
}
DefaultSlotChainBuilder#build
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// 通过SPI机制,加载多个ProcessorSlot
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;
}
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
ProcessorSlot的各自含义:
- NodeSelectorSlot 主要用于构建调用链。
- ClusterBuilderSlot 用于集群限流、熔断。
- LogSlot用于记录日志。
- StatisticSlot 用于实时收集实时信息。
- AuthoritySlot 用于权限校验的。
- SystemSlot 用于验证系统级别的规则。
- FlowSlot 实现限流机制。
- DegradeSlot 实现熔断机制。
下一篇将介绍Sentinel滑动时间窗口源码,重点就是StatisticSlot 、FlowSlot 。