Sentinel的工作原理
Sentinel的核心骨架是ProcessorSlotChain,其是将不同的Slot(槽)按顺序串在一起(责任链模式),从而将不同的功能组合在一起。Slot如图分为上下两部分:统计数据构建部分(statistic)和判断部分(rule checking)。系统会为每个资源创建一套SlotChain。
Slot实现类简介
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;(构建资源调用链路的树)
ClusterBuilderSlot
用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等,这些信息将用作为多维度限流,降级的依据;(仅构建ClusterNode来存储数据)
StatisticSlot
用于记录、统计不同纬度的 runtime 指标监控信息;(通过该Slot计算统计后给Node赋值)
FlowSlot
用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;(对应流控规则功能,ParamFlowSlot 则对应热点规则功能)
AuthoritySlot
根据配置的黑白名单和调用来源信息,来做黑白名单控制;(对应授权规则功能)
DegradeSlot
通过统计信息以及预设的规则,来做熔断降级;(对应降级规则功能)
SystemSlot
通过系统的状态,例如 load1 等,来控制总的入口流量;(对应系统规则功能)
SPI机制
ProcessorSlotChain实现于ProcessorSlot接口,Sentinel将ProcessorSlot作为SPI接口进行扩展,使用户可以自定义Slot并编排Slot间的顺序。
SPI是什么
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口。在resources目录下新建META-INF/services目录并且在这个目录下新建一个与上述接口的全类名一致的文件,然后在文件中写入接口实现类的全类名。
通过ServiceLoader.load(ProcessorSlot.class) 便可以获取执行这些实现类。(SpringBoot 的自动装配便是借鉴了SPI来读取META-INF/spring.factories下的配置类来完成自动配置)
Context简介
Context代表调用链路上下文,每个资源(Entry)操作必须属于一个Context,如果没有指定则会创建name为sentinel_default_context的默认Context。
Context与Entry的关系
ContextUtil.enter("entrance1", "appA");//创建来自appA访问的Context
Entry node1 = null;
Entry node2 = null;
try {
node1 = SphU.entry("resource1");//获取资源resource1的操作对象Entry
//没有抛异常代表检查通过,业务处理
node2 = SphU.entry("resource2");//获取资源resource2的操作对象Entry
//同理
} catch (BlockException exception) {
//被限流,降级处理
} finally {
if (node1 != null) {
node1.exit();
}
if (node2 != null) {
node2.exit();
}
ContextUtil.exit();
}
Node间的关系
如果在上面entrance1上下文存在的同时,也有一个entrance2上下文在对resource2和resource3进行操作,便会形成如下树结构。
Node:用于完成数据统计的接口
EntranceNode:入口节点,一个Context对应一个,用于统计当前Context的总体流量
DefaultNode:默认节点,用于统计一个资源在当前Context中的流量数据
ClusterNode:集群节点,用于统计一个资源在所有Context中的总体流量
StatisticNode:统计节点,用于完成数据统计
源码分析
在SentinelAutoConfiguration自动配置类中可以找到一个自动加载的SentinelResourceAspect
,它是一个切面,其切入点为@SentinelResource注解,通过环绕通知进行增强。在这里便看到了上面熟悉的SphU.entry方法。
跟入entry方法中便看到真正的入口方法entryWithPriority
entryWithPriority方法分析
这个方法分为获取Context、查找SlotChain、操作资源、关闭资源共4步。
Context的创建分析
这个方法分为尝试获取、加锁创建、绑定返回共3步。注意其写入缓存map的写法是为了防止对共享集合的读操作出现脏数据,既“迭代稳定性问题”。
SlotChain的查找分析
lookProcessChain方法分为缓存获取(与资源对象绑定)、加锁创建两步,下面为SlotChain构造者的创建方法:
看看DefaultProcessorSlotChain这个单向链表,AbstractLinkedProcessorSlot类中包含了一个同类型的next属性:
SlotChain分析
获取到SlotChain后返回entryWithPriority方法进行操作资源,跟入DefaultProcessorSlotChain的entry方法:
随后通过继承关系调用到真正Slot实现类的entry方法:
NodeSelectorSlot
entry方法分为缓存获取、加锁创建(并添加树节点)、调用fireEntry转向下一个节点三步。
StatisticSlot
先调用fireEntry执行SlotChain中后续的Slot完成所有规则检测,若没抛出BlockException则为通过,增加线程数和请求计数1。
DegradeSlot(FlowSlot涉及时间窗内容放最后)
获取到资源相关的所有熔断器逐个进行检测,熔断器通过三种状态来判断是否通过。
滑动时间窗算法
时间窗限流算法:以T时间为一个时间窗,当时间窗口内的统计数超过阈值时限流。但跨时间窗口时统计的数量其实是可以超出阈值的。
滑动时间窗限流算法:时间窗不再是固定的而是随着时间推移滑动的,当某个时间点要判断限流时往前计算T-i时间内的通过数。但在高并发下需要判断限流的时间点出现会很频繁,也就意味着要多次计算多个T-i的数据,这里面的计算大部分都是重复的。
滑动时间窗改进版:创建一个采样数组,将时间窗拆分为多个样本窗口,样本时间窗口内的通过数存储在对应采样数组位置中,要计算当前时间点通过数时,计算出当前时间点所在样本时间窗口位置然后将前面的通过数和当前采样通过数相加即可。
数据统计的添加
从StatisticSlot中跟进addPassRequest方法,在分析前需要认识几个对应的元素:
先从LeapArray中获取当前时间点所在的样本窗口。
计算样本窗口起始时间戳是因为采样数组的长度是固定的(即样本窗口数)而时间是流动的,索引上样本窗口可能已经过时了不能使用,所以需要比较起始时间。
然后让MetricBucket添加PASS维度数据。
数据统计的使用
FlowSlot
checkFlow方法由获取流控规则与通过统计数据进行判断两步组成。
(canPassCheck中通过来源为空判断是否请求发起者直接通过)
只要将当前时间窗中已统计数加上当前请求通过计算数与阈值数比较便可。