实战Alibaba Sentinel:深度解析微服务高并发流量治理
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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如下。