微服务高并发概念与核心类:调用链上下文与入口类
调用链上下文与入口类
理解 Context 与 Entry 核心类也是理解 Sentinel 整个工作流程的关键,其中,Entry 核心类还会涉及“调用树”这一概念。读者通过学习本节可加深对 2.1 节介绍的调用链与调用树的理解。
Context
Context 即调用链上下文,贯穿整条调用链。在响应式编程项目中,Context 隐藏在订阅过程中传递,而在非响应式编程项目中,Context 可通过 ThreadLocal 在调用链上传递。
我们经常会在一些框架中见到 Context 的身影,Context 可用于减少参数的深层传递,减少方法的参数个数,具体做法是将一些调用链上被多个方法使用的参数提取到 Context 中,使调用链上方法的执行强依赖 Context,即 Context 作为这些方法执行所依赖的环境。
Context 还可用于弥补前期框架设计上的不足。试想一下,如果调用链上的某个方法需要增加一个参数,那么可能从调用链入口方法开始,每个方法都会增加一个参数,并且由入口方法将参数层层向下传递,尽管参数途经的很多方法都不需要用到它,但是都必须接收它并将它向下传递。
只要增加一个参数,就需要改动很多代码,这显然是不合理的,而使用 Context 就可以绕过层层传递。在调用链入口处将新增参数写入 Context,使调用链上的任何方法都可以从 Context 中获取该参数。
在 Sentinel 中,Context 维持着入口节点(entranceNode)、调用链上当前资源的 Entry 实例(curEntry)及调用来源(origin)等信息。
Context 类定义了如下字段:
name:调用链的入口名称。
entranceNode:调用链的入口节点,类型为 EntranceNode。
curEntry:调用链上当前资源的 Entry 实例,类型为 CtEntry。
origin:调用来源,即服务消费者的名称或服务消费者的 IP,由服务消费者传递过来。
提示:如果服务提供者和服务消费者都是 WebMvc 应用,且都使用 Sentinel 的 WebMvc 适配模块,那么 Sentinel 会尝试从请求头中获取 S-user 参数的值作为调用来源的名称,只要服务消费者在请求头传递了这个参数,服务提供者就能够获取。
调用链上 Context 实例的创建时机为,调用链上 ContextUtil 类的 enter 方法被首次调用时。
假设服务 B 提供一个查询天气预报的接口给服务 A 调用,且服务 B 实现天气预报查询的接口需要调用第三方服务 C 的接口实现,同时服务 B 是一个 WebMvc 应用,在调用服务 C 接口时使用 OpenFeign 框架,那么服务 B 既使用了 Sentinel 的 WebMvc 适配模块,也使用了 Sentinel 的 OpenFeign 适配模块。
在此例中,当服务 B 收到服务 A 的请求时,会创建一个入口名称为 sentinel_spring_web_context 的 Context 实例,当服务 B 在向服务 C 发起接口调用时,由于调用链上已经存在一个 Context 实例,因此不会创建新的 Context 实例。
Entry
在调用链上,一个资源对应一个 Entry 实例。可以从 Context 中获取调用链上当前访问到的资源的 DefaultNode 实例,而 DefaultNode 实例实际是从 Context 实例的 curEntry 字段中获取的。
Context 实例的 curEntry 字段引用当前资源的 DefaultNode 实例,以及资源相对当前调用来源的 StatisticNode 实例。
Entry 抽象类定义了如下字段:
curNode:当前资源的 DefaultNode 实例。
originNode:资源相对当前调用来源的 StatisticNode 实例。
resourceWrapper:资源 ID。
CtEntry 是 Entry 的直接子类,CtEntry 用于维护父子 Entry 关系,每一次调用 SphU 类的 entry 方法都会创建一个 CtEntry 实例。
CtEntry 类中声明的字段信息如下述代码所示:
parent:当前 Entry 实例指向的父 Entry。
child:当前 Entry 实例指向的下一个 Entry 实例。
chain:当前资源的 ProcessorSlotChain 实例,Sentinel 会为每个资源创建且仅创建一个 ProcessorSlotChain 实例。
context:调用链上的 Context 实例。
如果调用链上多次调用 SphU 类的 entry 方法(调用链上有多个资源),那么调用链上这些资源的 CtEntry 实例会构成一个双向链表。每次创建 CtEntry 实例都会将 Context 实例的 curEntry 字段设置为这个新的 CtEntry 实例。双向链表的作用就是在调用 CtEntry 类的 exit 方法时,能够将 Context 实例的 curEntry 字段还原为引用调用链上前一个 CtEntry 实例。
类似于 Java 虚拟机上栈桢的入栈和出栈。
以前面的天气预报例子进行说明,在服务 B 收到服务 A 的请求时,Sentinel 在拦截器中会调用 SphU 类的 entry 方法创建一个 CtEntry 实例,取名为 ctEntry1,此时的 ctEntry1 的父节点为空。当服务 B 向服务 C 发起调用时,Sentinel 会拦截请求的发起,调用 SphU 类的 entry 方法创建一个 CtEntry 实例,取名为 ctEntry2,此时 ctEntry2 的父节点指向 ctEntry1,ctEntry1 的子节点指向 ctEntry2,如图所示:
调用树与 ROOT 节点
Sentinel 的 Constants 常量类用于声明全局静态常量。Constants 类有一个 ROOT 静态字段,类型为 EntranceNode,被用作资源调用树的根节点。
调用树根节点(ROOT)在 Constants 类中的声明如下:
在调用链上,当 ContextUtil 类的 enter 方法首次被调用时,如果不存在与 enter 方法传入的入口名称相同的 EntranceNode 实例,就会创建一个 EntranceNode 实例。调用链上的 Context 实例的 entranceNode 字段引用的就是该 EntranceNode 实例,而 ROOT 的子节点(childList)存储的是整个应用中的所有 EntranceNode 实例。
根节点、调用链入口节点与资源的 DefaultNode 节点组成的调用树如图所示:
入口相同的调用链不一定就是同一条调用链,如图 2.8 所示,资源 GET:/v1/demo1 所在的调用链与资源 GET:/v1/demo2 所在的调用链的入口都是 sentinel_spring_web_context,但这两条调用链是两条不同的调用链。
调用树上的非资源节点类型都是 EntranceNode,调用树上的资源节点类型都是 DefaultNode。
处理器插槽:ProcessorSlot
ProcessorSlot 是 Sentinel 实现限流、熔断降级、系统自适应等功能的切入点。Sentinel 提供的 ProcessorSlot 可以分为两类:一类是负责资源指标数据统计的 ProcessorSlot,一类是实现限流、熔断等流量控制功能的 ProcessorSlot。
实现资源指标数据统计的 ProcessorSlot 的参数如下:
NodeSelectorSlot:为调用链上的资源创建 DefaultNode 实例,相同调用链上的每个资源仅会创建一个 DefaultNode 实例。
ClusterBuilderSlot:为调用链上的资源创建 ClusterNode 实例,以及对于不同调用来源,为调用链上的资源都创建一个 StatisticNode 实例。
StatisticSlot:真正用于实现资源指标数据统计的处理器插槽,它会先调用后续的 ProcessorSlot 类的 entry 方法判断是否放行请求,再根据结果执行相应的资源指标数据统计操作。
实现流量控制功能的 ProcessorSlot 的参数如下:
AuthoritySlot:实现黑白名单限流。
SystemSlot:实现系统自适应限流。
FlowSlot:实现 QPS/Threads 限流。
DegradeSlot:实现熔断降级。
小结
通过本文的学习,读者应对 Sentinel 的一些概念、核心类及 Sentinel 提供的 ProcessorSlot 有了一定的了解。
资源指标数据的统计及每个 ProcessorSlot 可实现的功能等内容将会在后续进行详细分析。
评论