【前面的话】在前文 Sentinel入门指北 中简单介绍了一下Sentinel
,今天就来具体说一下Sentinel
的基本原理。
壹、概述
在 Sentinel
里面,所有的资源都对应一个资源名称以及一个 Entry
。Entry
可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API
显式创建;每一个 Entry
创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的RT
,QPS
,thread count
等等,这些信息将用作为多维度限流,降级的依据;StatisticSlot
则用于记录、统计不同纬度的runtime
指标监控信息;FlowSlot
则用于根据预设的限流规则以及前面slot
统计的状态,来进行流量控制;AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;DegradeSlot
则通过统计信息以及预设的规则,来做熔断降级;SystemSlot
则通过系统的状态,例如load1
等,来控制总的入口流量;
总体的框架如下:
Sentinel
将 ProcessorSlot
作为 SPI
接口进行扩展(1.7.2 版本以前 SlotChainBuilder
作为 SPI
),使得 Slot Chain
具备了扩展的能力。您可以自行加入自定义的 slot
并编排 slot
间的顺序,从而可以给 Sentinel
添加自定义的功能。
下面介绍一下各个 slot
的功能。
1.1、NodeSelectorSlot
这个 slot
主要负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。
1 | ContextUtil.enter("entrance1", "appA"); |
上述代码通过 ContextUtil.enter()
创建了一个名为 entrance1
的上下文,同时指定调用发起者为 appA
;接着通过 SphU.entry()
请求一个 token
,如果该方法顺利执行没有抛 BlockException
,表明 token
请求成功。
以上代码将在内存中生成以下结构:
1 | machine-root |
注意:每个
DefaultNode
由资源ID
和输入名称来标识。换句话说,一个资源ID
可以有多个不同入口的DefaultNode
。
1 | ContextUtil.enter("entrance1", "appA"); |
以上代码将在内存中生成以下结构:
1 | machine-root |
上面的结构可以通过调用 curl http://localhost:8719/tree?type=root
来显示:
1 | EntranceNode: machine-root(t:0 pq:1 bq:0 tq:1 rt:0 prq:1 1mp:0 1mb:0 1mt:0) |
1.2、ClusterBuilderSlot
此插槽用于构建资源的 ClusterNode
以及调用来源节点。ClusterNode
保持资源运行统计信息(响应时间、QPS、block 数目、线程数、异常数等)以及原始调用者统计信息列表。来源调用者的名字由 ContextUtil.enter(contextName,origin)
中的 origin
标记。可通过如下命令查看某个资源不同调用者的访问情况:curl http://localhost:8719/origin?id=caller
:
1 | id: nodeA |
1.3、StatisticSlot
StatisticSlot
是 Sentinel
的核心功能插槽之一,用于统计实时的调用数据。
clusterNode
:资源唯一标识的ClusterNode
的runtime
统计origin
:根据来自不同调用者的统计信息defaultnode
: 根据上下文条目名称和资源ID
的runtime
统计- 入口的统计
Sentinel
底层采用高性能的滑动窗口数据结构 LeapArray
来统计实时的秒级指标数据,可以很好地支撑写多于读的高并发场景。
1.4、FlowSlot
这个 slot
主要根据预设的资源的统计信息,按照固定的次序,依次生效。如果一个资源对应两条或者多条流控规则,则会根据如下次序依次检验,直到全部通过或者有一个规则生效为止:
- 指定应用生效的规则,即针对调用方限流的;
- 调用方为 other 的规则;
- 调用方为 default 的规则。
1.5、DegradeSlot
这个 slot
主要针对资源的平均响应时间(RT)以及异常比率,来决定资源是否在接下来的时间被自动熔断掉。
1.6、SystemSlot
这个 slot
会根据对于当前系统的整体情况,对入口资源的调用进行动态调配。其原理是让入口的流量和当前系统的预计容量达到一个动态平衡。
注意系统规则只对入口流量起作用(调用类型为 EntryType.IN
),对出口流量无效。可通过 SphU.entry(res, entryType)
指定调用类型,如果不指定,默认是EntryType.OUT
。
贰、 核心类解析
2.1、ProcessorSlotChain
Sentinel
的核心骨架,将不同的 Slot
按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级、系统保护)组合在一起。slot chain
其实可以分为两部分:统计数据构建部分(statistic)和判断部分(rule checking)。核心结构:
目前的设计是 one slot chain per resource
,因为某些 slot
是 per resource
的(比如 NodeSelectorSlot
)。
2.2、Context
Context
代表调用链路上下文,贯穿一次调用链路中的所有 Entry
。Context
维持着入口节点(entranceNode)、本次调用链路的 curNode
、调用来源(origin)等信息。Context
名称即为调用链路入口名称。
Context
维持的方式:通过 ThreadLocal
传递,只有在入口 enter
的时候生效。由于 Context
是通过 ThreadLocal
传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context
,因此需要手动通过 ContextUtil.runOnContext(context, f)
来变换 context
。
2.3、Entry
每一次资源调用都会创建一个 Entry
。Entry
包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。
CtEntry
为普通的 Entry
,在调用 SphU.entry(xxx)
的时候创建。特性:Linked entry within current context(内部维护着 parent 和 child)
需要注意的一点:CtEntry
构造函数中会做调用链的变换,即将当前 Entry
接到传入 Context
的调用链路上(setUpEntryFor)。
资源调用结束时需要 entry.exit()
。exit
操作会过一遍 slot chain exit
,恢复调用栈,exit context
然后清空 entry
中的 context
防止重复调用。
2.4、Node
Sentinel
里面的各种种类的统计节点:
StatisticNode
:最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构。DefaultNode
:链路节点,用于统计调用链路上某个资源的数据,维持树状结构。ClusterNode
:簇点,用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据(类型为StatisticNode
)。特别地,Constants.ENTRY_NODE
节点用于统计全局的入口资源数据。EntranceNode
:入口节点,特殊的链路节点,对应某个Context
入口的所有调用数据。Constants.ROOT
节点也是入口节点。
构建的时机:
EntranceNode
:在ContextUtil.enter(xxx)
的时候就创建了,然后塞到Context
里面。NodeSelectorSlot
:根据context
创建DefaultNode
,然后set curNode to context
。ClusterBuilderSlot
:首先根据resourceName
创建ClusterNode
,并且set clusterNode to defaultNode
;然后再根据origin
创建来源节点(类型为StatisticNode
),并且set originNode to curEntry
。
几种 Node
的维度(数目):
ClusterNode
的维度是resource
DefaultNode
的维度是resource * context
,存在每个NodeSelectorSlot
的map
里面EntranceNode
的维度是context
,存在ContextUtil
类的contextNameNodeMap
里面- 来源节点(类型为
StatisticNode
)的维度是resource * origin
,存在每个ClusterNode
的originCountMap
里面
2.5、StatisticSlot
StatisticSlot
是 Sentinel
最为重要的类之一,用于根据规则判断结果进行相应的统计操作。
entry
的时候:依次执行后面的判断 slot
。每个 slot
触发流控的话会抛出异常(BlockException
的子类)。若有 BlockException 抛出,则记录 block 数据;若无异常抛出则算作可通过(pass),记录 pass 数据。
exit
的时候:若无 error(无论是业务异常还是流控异常)
,记录 complete(success)
以及 RT
,线程数-1
。
记录数据的维度:线程数+1
、记录当前 DefaultNode
数据、记录对应的 originNode
数据(若存在 origin
)、累计 IN
统计数据(若流量类型为 IN
)。
【后面的话】最后是我自己实践自定义调用链的源码 。