Sentinel 的整体工作流程分析
Sentinel 的整体工作流程如下:
调用 ContextUtil#enter 方法创建 Context 实例,创建的 Context 实例会被存储到 ThreadLocal 中,在调用链上可以随时获取该 Context 实例。
调用 SphU#entry 方法创建 CtEntry 实例,调用 ProcessorSlotChain#entry 方法。如果是首次访问资源,则需要为资源创建 ProcessorSlotChain。注册在 ProcessorSlotChain 上的每个 ProcessorSlot 都是一个流量切入点。
若在调用 SphU#entry 方法时抛出 BlockException,则说明当前请求被拒绝;若在调用业务方法时抛出异常,则会收集异常指标数据。
在调用 SphU#entry 方法后,需要确保调用一次 SphU#entry 方法返回的 Entry 实例的 exit 方法,并由 Entry 实例调用 ProcessorSlotChain 的 exit 方法。
在调用 ContextUtil#entry 方法后,需要确保调用一次 ContextUtil#exit 方法,将 Context 实例从 ThreadLocal 中移除。
如果不借助 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、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 方法的调用过程一样。
在前面介绍 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 如下:
在处理一次请求的过程中,Sentinel 会为调用链上的每个资源都创建一个 CtEntry 实例,每个 CtEntry 实例引用资源对应的 ProcessorSlotChain。CtEntry 维护双向链表的目的:在下一个资源方法执行结束时,能够将 Context 实例引用的 CtEntry 实例回退为引用上一个资源方法的 CtEntry 实例,以便随时通过 Context 实例获取当前资源的 ProcessorSlotChain。
最后,再总结一下 Sentinel 的整体工作流程:
调用 ContextUtil#enter 方法创建 Context 实例,创建的 Context 实例会被存储到 ThreadLocal 中,在调用链上可以随时获取该 Context 实例。
调用 SphU#entry 方法创建 CtEntry 实例,调用 ProcessorSlotChain#entry 方法。如果是首次访问资源,则需要为资源创建 ProcessorSlotChain。注册在 ProcessorSlotChain 上的每个 ProcessorSlot 都是一个流量切入点。
若在调用 SphU#entry 方法时抛出 BlockException,则说明当前请求被拒绝;若在调用业务方法时抛出异常,则会收集异常指标数据。
在调用 SphU#entry 方法后,需要确保调用一次 SphU#entry 方法返回的 Entry 实例的 exit 方法,并由 Entry 实例调用 ProcessorSlotChain 的 exit 方法。
在调用 ContextUtil#entry 方法后,需要确保调用一次 ContextUtil#exit 方法,将 Context 实例从 ThreadLocal 中移除。
评论