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如下:
在处理一次请求的过程中,Sentinel会为调用链上的每个资源都创建一个CtEntry实例,每个CtEntry实例引用资源对应的ProcessorSlotChain。CtEntry维护双向链表的目的:在下一个资源方法执行结束时,能够将Context实例引用的CtEntry实例回退为引用上一个资源方法的CtEntry实例,以便随时通过Context实例获取当前资源的ProcessorSlotChain。
Sentinel的整体工作流程可总结如下:
(1)调用ContextUtil#enter方法创建Context实例,创建的Context实例会被存储到ThreadLocal中,在调用链上可以随时获取该Context实例。
(2)调用SphU#entry方法创建CtEntry实例,调用ProcessorSlotChain#entry方法。如果是首次访问资源,则需要为资源创建ProcessorSlotChain。注册在ProcessorSlotChain上的每个ProcessorSlot都是一个流量切入点。
(3)若在调用SphU#entry方法时抛出BlockException,则说明当前请求被拒绝;若在调用业务方法时抛出异常,则会收集异常指标数据。
(4)在调用SphU#entry方法后,需要确保调用一次SphU#entry方法返回的Entry实例的exit方法,并由Entry实例调用ProcessorSlotChain的exit方法。
(5)在调用ContextUtil#entry方法后,需要确保调用一次ContextUtil#exit方法,将Context实例从ThreadLocal中移除。
本文给大家讲解的内容是深度解析微服务高并发了解整体工作流程:Sentinel的整体工作流程分析
下篇文章给大家讲解的内容是深度解析微服务高并发资源指标数据统计:基于滑动窗口实现资源指标数据统计
感谢大家的支持!
领取专属 10元无门槛券
私享最新 技术干货