3.3 Sentinel的整体工作流程分析
如果不借助Sentinel提供的适配模块,则可以采用下面的方法使用Sentinel,代码如下。
① 调用ContextUtil#enter方法。
② 调用SphU#entry方法,该方法会为资源创建Entry实例。
③ 若抛出异常,且非BlockException异常,则调用Tracer#trace方法统计异常。
④ 调用资源的Entry#exit方法。
⑤ 调用ContextUtil#exit方法。
1. ContextUtil#enter方法
ContextUtil#enter方法负责在调用链入口创建Context实例,以及为调用链创建入口节点,即EntranceNode实例,源码如下。
① ContextUtil使用ThreadLocal存储Context,若当前调用链上已经存在Context,则使用已经存在的Context,否则创建Context。
② 尝试从缓存中获取已经存在的入口节点。
③ 若不存在入口节点,则创建入口节点,入口节点的名称与Context的名称相同。
④ 将入口节点添加到调用树的ROOT节点的子节点中。
入口节点是一个调用链入口,只能被创建一个,并且作为调用树根节点(ROOT)的子节点。
2. SphU#entry方法
Sentinel的核心骨架是ProcessorSlotChain,所以核心的流程是一次SphU#entry方法的调用及一次Entry#exit方法的调用。
SphU#entry方法会调用CtSph#entry方法。CtSph的作用如下:为资源创建ResourceWrapper对象并为资源构造一个全局唯一的ProcessorSlotChain,为资源创建CtEntry并将CtEntry赋值给当前调用链上下文的curEntry字段,调用ProcessorSlotChain#entry方法完成一次ProcessorSlot单向链表的entry方法调用。
ProcessorSlotChain#entry方法的调用过程如图3.1所示。
图3.1 ProcessorSlotChain#entry方法的调用过程
3. Tracer#trace方法
当调用SphU#entry方法或执行资源方法抛出异常时,如果抛出的是非BlockException异常,则调用Tracer#trace方法统计异常指标。
Tracer#trace方法最终会调用Tracer#traceExceptionToNode方法。Tracer#traceExceptionToNode方法的源码如下。
Sentinel只为一个资源创建一个ClusterNode,用于统计一个资源的全局指标数据,熔断降级与限流降级都使用了这个ClusterNode。
ClusterNode类的trace方法的实现代码如下。
4. Entry#exit方法
在调用SphU#entry方法时,SphU#entry方法会返回一个CtEntry实例,那么在调用资源方法后,无论是出现异常还是正常执行完成,都需要调用一次该CtEntry实例的exit方法。
下面是CtEntry实例的exit方法的实现,为了简短且易于理解,给出的是删减后的exitForContext方法的源码。
CtSph在创建CtEntry实例时,将资源的ProcessorSlotChain赋值给了CtEntry实例,所以在调用CtEntry实例的exit方法时,CtEntry实例能够拿到当前资源的ProcessorSlotChain,并调用ProcessorSlotChain的exit方法完成一次单向链表的exit方法调用。其调用过程与ProcessorSlotChain#entry方法的调用过程一样。
在第2章介绍CtEntry时提到过,CtEntry用于维护父子Entry,每调用一次SphU#entry方法都会创建一个CtEntry实例。如果在应用处理一次请求的路径上多次调用SphU#entry方法,那么这些CtEntry实例会构成一个双向链表。在每次创建CtEntry实例时,都会将Context实例的curEntry字段指向这个新的CtEntry实例,双向链表的作用就是在调用CtEntry实例的exit方法时,能够将Context实例的curEntry字段指向上一个CtEntry实例。
5. ContextUtil#exit方法
ContextUtil#enter方法在调用链入口创建Context实例,且创建的Context实例会被存储到ContextUtil类的一个类型为ThreadLocal的静态变量中,因此在退出方法之前必须调用一次ContextUtil#exit方法,从而将Context实例从ThreadLocal中移除。
ContextUtil#exit方法的源码如下。
• 如果Context实例的curEntry字段值为空,则说明所有SphU#entry方法创建的Entry实例都执行了一次exit方法,此时就可以将Context实例从ThreadLocal中移除。
ContextUtil#enter方法与ContextUtil#exit方法并不是必须调用的,当不需要为资源区分不同调用链入口的配置限流规则时可以被省略,但Context实例是调用链上方法执行所依赖的环境,因此,在默认的情况下,Sentinel会自动创建一个调用链入口名称为sentinel_default_context的Context实例,同时会创建一个调用链入口名称为sentinel_default_context的入口节点。
省略调用ContextUtil#enter方法与ContextUtil#exit方法的demo如下。