微服务组件源码4——Setinel源码整体流程
流程图和代码处理流程(核心)
* SphU.entry方法主流程图

* Context、Entry、Node关系图

* 整体流程
Entry entry = null;
// 务必保证 finally 会被执行
try {
// 1、进行Sentinel拦截
// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
// EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
// entry方法内会封装了资源的名称StringResourceWrapper(name, type),再最终会进入CtSph#¥entryWithPriority方法执行主流程
entry = SphU.entry("自定义资源名");
// 被保护的业务逻辑
// do something...
} catch (BlockException ex) {
// 2、抛出BlockException基类异常。说明业务资源访问阻止,被限流或被降级
// 进行相应的处理操作,用户可以自定义的处理,如进行降级或者返回异常
} catch (Exception ex) {
// 3、其他异常,如业务代码抛出的异常。也可以向上抛出
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
// 4、正常执行完成。退出的情况
// 务必保证 exit,务必保证每个 entry 与 exit 配对
// (同下)this.exit(count, new Object[0];
if (entry != null) {
entry.exit();
}
}
* SphU.entry最终会进入CtSph类的entryWithPriority方法
//CtSph类
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
// 1. 获取当前线程Context
// * ContextUtil里存在ThreadLocal<Context> contextHolder保存了当前线程的Context;
// * ContextUtil里存在Map<String, DefaultNode>contextNameNodeMap 记录所有Context对应的EntranceNode
Context context = ContextUtil.getContext();
// 当Context数量超过了阈值,不会做任何规则校验
// 一个进程中,Sentinel只能允许最多2000个Context(硬编码),超出2000个都会返回NullContext,后续不会执行任何规则校验,直接放行;
if (context instanceof NullContext) {
return new CtEntry(resourceWrapper, null, context);
}
// 2. 如果用户没有主动创建Context,就会创建一个默认的上下文sentinel_default_context
// 其底层调用ContextUtil#trueEnter进行创建
// * 先判断Context上限,超过就返回NullContext,不会创建实际Context
// * 创建EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);作为全局Constants.ROOT的子节点,再put(上下文名称,EntranceNode)进去contextNameNodeMap
// * 创建Context并放入ThreadLocal
// context = new Context(EntranceNode, name);
// context.setOrigin(origin);
// contextHolder.set(context)
if (context == null) {
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}
// 3. 获取Slot链
// *lookProcessChain方法加载Resource对应ProcessorSlotChain,ProcessorSlotChain包含了所有通过SPI(META-INF/services/")机制加载的ProcessorSlot
// * 全局的Map<ResourceWrapper, ProcessorSlotChain> chainMap保存着资源名称对应的Slot链,同一个name的Resource资源对应同一个ProcessorSlotChain实例
// * 一个进程中,Sentinel只能允许最多6000个Resource(硬编码),超出6000个lookProcessChain方法会返回null,后续不会执行任何规则校验,直接放行;
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
// 4. 构造CtEntry,构造时将这个Entry接入Context中的Entry链表尾部,封装好对应的Slot链和Context
// 再一个Slot链内,每次当前Slot执行完自己的职责后(责任链),会调用抽象类中的fireEntry方法,执行下一个Slot的entry方法。
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
// 5. 执行所有规则校验
// * 默认存在的Slot链如下
// NodeSelectorSlot:构建资源(Resource)的路径(DefaultNode),用树的结构存储。
// ClusterBuilderSlot:构建ClusterNode,用于记录资源维度的统计信息。
// StatisticSlot:使用Node记录指标信息,如RT、Pass/Block Count,为后续规则校验提供数据支撑。
// AuthoritySlot:授权规则校验
// SystemSlot:系统规则校验
// ParamFlowSlot:热点参数流控规则校验
// FlowSlot:流控规则校验
// DegradeSlot:降级规则校验
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
//6. 以上步骤如果发生BlockException,需要先执行exit,再抛出异常(业务代码外层也会在finally里调用exit)
// * exit底层调用CtEntry的exitForContext
// 1. 执行所有Slot的exit方法,只有实现了自己exit逻辑,其他都是放行
- StatisticSlot:当ENtry的BlckErro为空,没有异常发生,那么进行Node的compelet记录)----业务代码发生异常的场景
- DegradeSlot:当ENtry的BlckErro为空,没有异常发生那么进行断路器的compelet记录----业务代码发生异常的场景
// 2. 执行所有exitHandlers(降级规则会用到)callExitHandlersAndCleanUp(context)
// 执行当前Entry上注册所有的exitHandlers,
// 在AbstractCircuitBreaker的fromOpenToHalfOpen里,从打开->半开时,就会注册一个exitHandler,如果因为异常原因的退出,那么会重新把断路器打开
// 3. context中entry链表移除当前entry
// 4. 当前entry.context = null,防止重复exit
//
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
// 内部错误,不太可能发生
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}
创建/获取线程上下文Context
概述
public class Context {
// 上下文名称
private final String name;
// EntranceNode 入口节点 --- 树,一般不同的Context其entranceNode不同,当用户不手动创建而使用默认的sentinel_default_context的上下文,其内会公用同一个EntranceNode
private DefaultNode entranceNode;
// 当前Entry --- 链表
private Entry curEntry;
// 来源
private String origin = "";
}
表示一次请求线程处理中的上下文信息,包括各个组件的引用
* 上下文名称,作为上下文的唯一标志
* EntranceNode 入口节点,由此可以找到整个线程执行过程中所创建的数据统计结点Node链
* 当前Entry结点,整个线程的每次调用拦截SphU.entry都会生成一个Entry,从而形成Entry链
* 来源标志,一个Context中只能存在一个来源,表示调用方
例如通过如下方法最终执行trueEnter(ContextName, origin),在这里就明确指定了来源,例如使用请求总的IP
String origin = parseOrigin(sRequest);
ContextUtil.enter(WebServletConfig.WEB_SERVLET_CONTEXT_NAME, origin);
创建Context(包含EntranceNode + curEntry)
* 可以在SphU.entry之前使用ContextUtil.enter(上下文名称,来源名称)创建Context,其底层调用ContextUtil.trueEnter(String name, String origin)
* 如果没有收到创建,那么SphU.entry中会使用一个名称为sentinel_default_context的默认上下文,其底层调用ContextUtil.trueEnter(sentinel_default_contex, “”)
注:所有名称为sentinel_default_context的默认创建的上下文都会公用同一个EntranceNode
* 在ContextUtil中创建
①一个本地线程变量contextHolder表示当前线程的Context
②contextNameNodeMap表示记录所有Context对应的EntranceNode ,key就是上下文名称
public class ContextUtil {
private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
}
* ContextUtil.enter代码流程
①先再次判断下当前线程上下文是否存在,存在就直接返回了
②获取上下文name对应的EntranceNode,localCacheNameMap/contextNameNodeMap 如果不存在需要创建。
如果是默认上下文的,会在加载时就创建了EntranceNode放入localCacheNameMap
③如果上下文数量超过MAX_CONTEXT_NAME_SIZE(2000)个,返回NullContext,不会创建实际Context
④创建入口EntranceNode,先对localCacheNameMap加锁,创建后放入localCacheNameMap中,并作为全局根结点Constants.ROOT的子节点
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
Constants.ROOT.addChild(node);
其中StringResourceWrapper封装了上下文名称和EntryType(表示调用方向)
⑤最后new Context(EntranceNode, name),设置好来源字段、把当前Context设置到本地线程contextHolder
public static Context enter(String name, String origin) {
if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
throw new ContextNameDefineException();
}
return trueEnter(name, origin);
}
protected static Context trueEnter(String name, String origin) {
// 1. 获取当前线程上下文,如果存在的话,不会创建新的上下文
Context context = contextHolder.get();
if (context == null) {
// 2. 获取上下文name对应的EntranceNode,如果不存在需要创建
Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
DefaultNode node = localCacheNameMap.get(name);
if (node == null) {
if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
// 3. 如果上下文数量超过MAX_CONTEXT_NAME_SIZE(2000)个,返回NullContext,不会创建实际Context
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 {
// 4. 创建name对应EntranceNode
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
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();
}
}
}
// 5. 创建Context并放入ThreadLocal
context = new Context(node, name);
context.setOrigin(origin);
contextHolder.set(context);
}
return context;
}
获取DefaultProcessorSlotChain(相同资源共用相同的Chain)
同一个name的资源,会使用同一个ProcessorSlotChain
在管理配置对一个name资源配置的所有规则会形成一个插槽链
Sentinel的SPI
* @Spi表示一个指定spi接口的实现类
注解添加在基于java SPI配置的实现类中,可以对这些服务提供的实现类进行控制,
比如别名互斥、单例、默认实现、优先级,这里还能都在SpiLoader<S>中进行处理
当一个类被此注解标志后,就表示此类作为某个SPI接口的实现类
public @interface Spi {
String value() default ""; //别名,通过设置别名,同一个SpiLoader中,相同别名的实现类只能存在一个
boolean isSingleton() default true; //是否单例,默认true
boolean isDefault() default false; //是否是默认实现类,默认false,同一个SpiLoader中,只能有一个默认实现类;
int order() default 0; //优先级
int ORDER_HIGHEST = Integer.MIN_VALUE;
int ORDER_LOWEST = Integer.MAX_VALUE;
}
* SpiLoader<S>加载器
- 一个Spi接口可以对应多个SpiLoader,但是一般对应一个接口
- 基于JAVA SPI中获取到类型的所有实现类,封装到一个SpiLoader类中,其中经过处理后的结果存于各个变量中
例如别名映射、排序
- 通过SpiLoader.of(S.class)可以获取到S类型的所有服务提供类对象。
public final class SpiLoader<S> {
// 加载SPI配置文件路径
private static final String SPI_FILE_PREFIX = "META-INF/services/";
// SPI接口 - SpiLoader实现类
private static final ConcurrentHashMap<String, SpiLoader> SPI_LOADER_MAP = new ConcurrentHashMap<>();
// 当前SpiLoader缓存的 SPI接口实现类
private final List<Class<? extends S>> classList = Collections.synchronizedList(new ArrayList<Class<? extends S>>());
// 当前SpiLoader缓存的 SPI接口实现类(有序)
private final List<Class<? extends S>> sortedClassList = Collections.synchronizedList(new ArrayList<Class<? extends S>>());
// 当前SpiLoader缓存的 SPI接口实现类别名 - SPI接口实现类
private final ConcurrentHashMap<String, Class<? extends S>> classMap = new ConcurrentHashMap<>();
// 当前SpiLoader缓存的 单例map k-className v-单例
private final ConcurrentHashMap<String, S> singletonMap = new ConcurrentHashMap<>();
// 当前SpiLoader是否已经加载所有SPI接口实现类
private final AtomicBoolean loaded = new AtomicBoolean(false);
// 当前SpiLoader对应Spi接口的默认实现类
private Class<? extends S> defaultClass = null;
// 当前SpiLoader对应Spi接口
private Class<S> service;
}
* 例如我们自定义一个Slot类,这时默认的几个Slot的顺序是不能去改变,我们需要把自定义的Slot的优先级作为最低。
在项目跟目录下创建以下文件,并在文件中写入上面定义的PrintLoveSlot的全限定类名。

代码如下,在FireEntry前后输出I Love You。
@Spi(order = -6500)
public class PrintLoveSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args)
throws Throwable {
System.out.println("I Love You,start");
fireEntry(context, resourceWrapper, obj, count, prioritized, args);
System.out.println("I Love You,end");
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
fireExit(context, resourceWrapper, count, args);
}
}
获取Slot链流程
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
* 同一个name的资源对应同一个ProcessorSlotChain实例(ResourceWrapper的equals和hasCode方法);
* 一个进程中,Sentinel只能允许最多6000个Resource(硬编码),超出6000个lookProcessChain方法会返回null,后续不会执行任何规则校验,直接放行;
* SPI机制加载所有ProcessorSlot,构造为DefaultProcessorSlotChain返回
- SpiLoader.of(SlotChainBuilder.class):基于SPI的生成一个SpiLoader对象,里面加载了SlotChainBuilder类型的实现类
- 再调用SpiLoader的loadFirstInstanceOrDefault,默认返回了DefaultSlotChainBuilder
* DefaultSlotChainBuilder.build方法获取 ProcessorSlotChain
- 和上面加载SlotChainBuilder的过程类似,这里是加载ProcessorSlot类型的实现类,并进行排序后返回
- SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
- 下图可以看到 META-INF/services/ + ProcessorSlot.fullClassName 的文件内容,发现有8个Slot。
loadInstanceListSorted 就是加载并排序,在每个Slot上面都标有@Order,加载后根据Order进行排序,生成有序列表。这里的过程很像Spring中对PostProcessor的处理。

* 代码流程
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap = new HashMap<ResourceWrapper, ProcessorSlotChain>()
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
// 1. 同一个name的资源,会使用同一个ProcessorSlotChain
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// 2. 如果资源数量超过了MAX_SLOT_CHAIN_SIZE(6000),则返回空,不做规则校验
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
// 3. SPI机制加载所有ProcessorSlot,构造为DefaultProcessorSlotChain返回
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;
}
public static ProcessorSlotChain newSlotChain() {
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
return slotChainBuilder.build();
}
@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
for (ProcessorSlot slot : sortedSlotList) {
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
continue;
}
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
}
ProcessorSlot架构

* ProcessorSlot接口,接口方法如下
①entry和exit:entry表示此slot的规则校验逻辑的处理,exit表示整个链接流程退出时的处理
②fireEntry和fireExit:提供责任链设计模式,调用下一个结点的entry和exit
* AbstractLinkedProcessorSlot类主要实现责任链的部分功能
①保存此slot实现类的下一个结点next,使用setNext
②实现了fireEntry和fireExit以提供责任链设计模式
- fireEntry:调用下一个结点的next.transformEntry即next.entry
- fireExit:调用下一个结点的next.exit
public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
//下一个Slot链
private AbstractLinkedProcessorSlot<?> next = null;
public AbstractLinkedProcessorSlot<?> getNext() {return next;}
public void setNext(AbstractLinkedProcessorSlot<?> next) {this.next = next;}
//调用下一个结点的entry
@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
if (next != null) {
//实际调用了next.entry
next.transformEntry(context, resourceWrapper, obj, count, prioritized, 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);
}
//调用下一个结点的entryexit
@Override
public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
if (next != null) {
next.exit(context, resourceWrapper, count, args);
}
}
}
* DefaultProcessorSlotChain类构建了责任链,同样需要实现ProcessorSlot接口
①创建了first头Slot,其entry和exit的实现很简单,直接调用了父类AbstractLinkedProcessorSlot的fireEntry和fireExit以触发下一个结点。而没有自己的特殊规则处理
②提供addFirst/addLast:把每一个加进来的Slot进行关联,即设置了每一个Slot的next(在AbstractLinkedProcessorSlot中),从而形成一条单向的链表
③entry和exit的实现,调用first结点的entry和exit
public class DefaultProcessorSlotChain extends ProcessorSlotChain {
//1、头结点
//其entry和exit的实现很简单,直接调用了父类AbstractLinkedProcessorSlot的fireEntry和fireExit。而没有自己的特殊规则处理
AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)throws Throwable {
super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
super.fireExit(context, resourceWrapper, count, args);
}
};
AbstractLinkedProcessorSlot<?> end = first;
//2、尾插法,把即将加入的结点添加到end结点的next变量
@Override
public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
end.setNext(protocolProcessor);
end = protocolProcessor;
}
@Override
public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {
protocolProcessor.setNext(first.getNext());
first.setNext(protocolProcessor);
if (end == first) {
end = protocolProcessor;
}
}
@Override
public void setNext(AbstractLinkedProcessorSlot<?> next) {
addLast(next);
}
@Override
public AbstractLinkedProcessorSlot<?> getNext() {
return first.getNext();
}
//3、SlotChain的entry和exit的实现
//调用first结点的entry和exit
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
throws Throwable {
first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
first.exit(context, resourceWrapper, count, args);
}
}
* 具体的实现类,如
①每一个slot实现类都必须实现entry和exit,此时进行规则校验时,此slot的规则处理以及整个链接流程退出时的处理
②在entry和exit的最后,都需要调用父类的fireEntry和fireExit以触发下一个结点的方法
public class AuthoritySlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
throws Throwable {
//自己的校验晾逻辑,如果不通过,就会抛出AuthorityException 异常,从而终止整个流程
checkBlackWhiteAuthority(resourceWrapper, context);
//父类方法,调用下一个结点的entry
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
//父类方法,调用下一个结点的exit
fireExit(context, resourceWrapper, count, args);
}
void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
//略
}
}
创建Entry(可看做SphU.entry的抽象)
概述

实现类tEntry的作用
* CtEntry中聚合了Context、Slot、Resource、以及保存了当前Context中对应的originNode、和当前Resource对应的DefaultNode
* 一个CtEntry表示一个SphU.entry的抽象
运行进行嵌套的调用SphU.entry进行拦截的,这样的话SphU.entry在不同代码调用点都可以当做一个CtEntry,并会形成一个调用链
* 每一个Entry也保存了前后结点的引用
* 提供了exit的实现,以在校验过程中出现BlockException或者正常退出时,做一些处理
创建CtEntry加入Context的Entry调用链
Entry e = new CtEntry(resourceWrapper, chain, context);
*在构造器中进行操作
- 设置了当前线程Context、当前资源的Slot链、Resource,
- 通过setUpEntryFor,将当前Entry前插加入了当前线程Context中的Entry调用链
表示当前正在执行的是本次SphU.entry调用
class CtEntry extends Entry {
// 上一个入口Entry
protected Entry parent = null;
// 下一个Entry
protected Entry child = null;
// Slot插槽
protected ProcessorSlot<Object> chain;
// 上下文
protected Context context;
protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
//设置了父类的两个变量
//this.resourceWrapper = resourceWrapper;
//this.createTime = TimeUtil.currentTimeMillis();
super(resourceWrapper);
this.chain = chain;
this.context = context;
setUpEntryFor(context);
}
// 将当前Entry加入上下文Entry链表
private void setUpEntryFor(Context context) {
if (context instanceof NullContext) {
return;
}
this.parent = context.getCurEntry();
if (parent != null) {
((CtEntry) parent).child = this;
}
context.setCurEntry(this);
}
}
开始执行Slot链
chain.entry(context, resourceWrapper, null, count, prioritized, args);
* chain即为之前到的当前资源对应的Slot链,调用其entry方法会一次执行责任链上的每一个Slot,默认存在的Slot链执行顺序
- NodeSelectorSlot:构建资源(Resource)的路径(DefaultNode),用树的结构存储。
- ClusterBuilderSlot:构建ClusterNode,用于记录资源维度的统计信息。
- StatisticSlot:使用Node记录指标信息,如RT、Pass/Block Count,为后续规则校验提供数据支撑。
- AuthoritySlot:授权规则校验
- SystemSlot:系统规则校验
- ParamFlowSlot:热点参数流控规则校验
- FlowSlot:流控规则校验
- DegradeSlot:降级规则校验
正常结束/异常捕捉
* 正常执行完成
- 务必保证 exit,务必保证每个 entry 与 exit 配对
- 执行同下异常处理的entry.exit()
* 如果在执行期间抛出异常,需要特定的捕捉BlockException类型的异常,这些异常表示当前请求被规则拦截了
用户可以对其进行自定义的异常处理,当时需要先执行exit,再抛出异常给到最外层

* Entry的exit(count, args)的处理逻辑
①如果发生异常的Entry不是当前上下文执行的Entry,即是内嵌调用Entry发生错误了,那么需要依次调用外层的Entry,一一进行exit
②如果是当前执行的Entry发生异常
- 逆序的执行所有Slot的exit方法
- 执行当前Entry上所有的exitHandlers,这主要是为了Degrade降级规则的断路器服务
- context中entry链表移除当前entry
- 当前entry.context = null,防止重复exit
③接着向外抛出BlockException
try {
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
//6. 以上步骤如果发生BlockException,需要先执行exit,再抛出异常
// * exit底层调用CtEntry的exitForContext
// 1. 逆序的执行所有Slot的exit方法
// 2. 执行所有exitHandlers(降级规则会用到)callExitHandlersAndCleanUp(context)
// 执行当前Entry上所有的exitHandlers,这主要是为了Degrade降级规则的断路器服务
// 3. context中entry链表移除当前entry
// 4. 当前entry.context = null,防止重复exit
//
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
// 内部错误,不太可能发生
RecordLog.info("Sentinel unexpected exception", e1);
}
@Override
public void exit(int count, Object... args) throws ErrorEntryFreeException {
trueExit(count, args);
}
protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
if (context != null) {
if (context instanceof NullContext) {
return;
}
//如果发生异常的Entry不是当前上下文执行的Entry,即是内嵌调用Entry发生错误了,那么需要依次调用外层的Entry,一一进行exit
if (context.getCurEntry() != this) {
// Clean previous call stack.
CtEntry e = (CtEntry)context.getCurEntry();
while (e != null) {
e.exit(count, args);
e = (CtEntry)e.parent;
};
throw new ErrorEntryFreeException();
} else {
//如果是当前执行的Entry发生异常
//先执行Slot链的exit,会递归的执行每一个Slot的exit
if (chain != null) {
chain.exit(context, resourceWrapper, count, args);
}
// Restore the call stack.
context.setCurEntry(parent);
if (parent != null) {
((CtEntry)parent).child = null;
}
if (parent == null) {
if (ContextUtil.isDefaultContext(context)) {
ContextUtil.exit();
}
}
clearEntryContext();
}
}
}
更多推荐
所有评论(0)