1. 前言
在之前的文章中,分析了Sentinel如何统计数据指标的。本篇文章主要是分析调用链路上的一些功能插槽的功能和实现,包括系统自适应、黑白名单控制、流量控制、熔断降级四个功能。
2. Sentinel功能插槽规则
2.1. 规则的定义
在分析各种功能插槽之前,先看一下Sentinel对规则的定义。
Sentinel使用Rule表示规则,Rule是一个接口,Rule只有一个方法,passCheck(),表示规则是否通过。
AbstractRule实现了Rule,是一个抽象类,里面定义了这个规则属于的资源,限制的app。所有具体的规则都是继承该规则。
2.2. 规则的加载或更新
当定义了具体的规则之后,需要加载规则到系统中,这个加载过程是由规则管理器负责的,比如定义了系统自适应规则SystemRule,会由对应的规则管理器SystemRuleManager加载该规则。
下面就以SystemRuleManager为例,分析规则的加载。
每一个规则管理器中都会有一个继承了SimplePropertyListener规则改变处理器作为变量,当规则改变之后,做相关的更新处理工作。每个规则管理器中会定义一个内部类作为该变量的具体实现,比如SystemRuleManager:
1 | 复制代码//创建一个文件处理器 |
可以看到,SystemRuleManager创建了一个SystemPropertyListener作为文件改变的处理器,SystemPropertyListener是其内部的一个内部类。
规则管理器中还会定义一个SentinelProperty,SentinelProperty代表Sentinel中的配置信息,在这个地方就是代表配置的规则。然后会将定义的规则处理器添加给规则,当规则变更的时候,使用对应的规则处理器进行处理。
1 | 复制代码private static SentinelProperty<List<SystemRule>> currentProperty = new DynamicSentinelProperty<List<SystemRule>>(); |
以SystemRule更新为例,当定义了规则之后,调用SystemRuleManager的loadRules()会进行规则的更新。
1 | 复制代码public static void loadRules(List<SystemRule> rules) { |
调用loadRules()的时候其实是调用DynamicSentinelProperty的updateValues()方法:
1 | 复制代码 public boolean updateValue(T newValue) { |
可以看到,DynamicSentinelProperty的updateValues()的方法中其实是调用规则改变处理器的configUpdate()方法,所以具体是如何更新的是每个规则管理器里面定义的内部类SimplePropertyListener。
以上就是规则的加载过程,各种规则的加载大致流程是一样的,只是具体的加载逻辑都是由自己的规则管理器中定义的SimplePropertyListener来负责更新。
3. 系统自适应限流
3.1. 什么是系统自适应限流
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
- CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围
0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。 - 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
以上文字摘抄至sentinel官方文档。
2.2. SystemRule
SystemRule代表系统自适应保护的规则定义,定义了load、cpu、qps、rt、thread维度的参数。
1 | 复制代码public class SystemRule extends AbstractRule { |
2.3. SystemRuleManager
SystemRuleManager是实现系统自适应保护的核心类,负责管理系统自适应规则,包括加载更新规则,检查当前入口调用是否满足规则。
1 | 复制代码public class SystemRuleManager { |
上面的参数主要含义为:
- 前面定义的十个参数主要是描述系统自适应保护参数的。
- checkSystemStatus:是否需要进行系统自适应保护。
- statusListener:负责获取系统运行参数的一个任务。
- listener:系统规则改变后的处理器。
- currentProperty:当前配置文件。
- scheduler:一个负责运行statusListener的定时任务。
再看静态代码块,主要是初始化scheduler,设置运行的任务和频率,并且将系统规则处理器添加给currentProperty。
SystemRuleManager中会负责规则的加载,这个部分在第二小节中已经讲过。
SystemRuleManager中还有一个最重要的方法,checkSystem(),负责校验进入的请求是否满足系统自适应设置的规则。
1 | 复制代码public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException { |
checkSystem()中具体的逻辑如上,具体实现原理可以参考官方文档。
2.4. SystemSlot
SystemSlot是负责系统自适应保护的功能插槽,每一个内部entry进入的时候都会进行系统自适应保护校验。SystemSlot很简单,主要是在entry()方法中调用 SystemRuleManager.checkSystem()方法进行系统自适应保护的检查,具体逻辑还是在SystemRuleManager中。
3. AuthoritySlot
黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
3.1. AuthorityRule
AuthorityRule代表对黑白名单设置的规则,其中主要包括个属性设置,一个是限制的来源名称,多个来源使用逗号分隔。还需要设置限制的时白名单还是黑名单。
1 | 复制代码private String limitApp; |
3.2. AuthorityRuleManager
AuthorityRuleManager和上一节的SystemRuleManager类似,是负责管理黑白名单的加载的。先看一下其中的属性定义:
1 | 复制代码public final class AuthorityRuleManager { |
和SystemRuleManager类似,AuthorityRuleManager也有一个规则改变的处理器RulePropertyListener,这个RulePropertyListener是AuthorityRuleManager的一个内部类,然后也有一个表示当前配置的配置类currentProperty,当需要加载更新规则的时候,逻辑和SystemRuleManager类似,所以不再描述。
3.3. AuthoritySlot
AuthoritySlot是负责处理黑白名单的功能插槽,当entry进入的时候,调用checkBlackWhiteAuthority()进行校验。
1 | 复制代码public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) |
可以看到,这个地方主要是获取所有的黑白名单规则,然后遍历调用AuthorityRuleChecker.passCheck()进行校验,校验的具体逻辑很简单,就不在此描述了。
4. 流量控制
流量控制原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
4.1. FlowRule
FlowRule代表流量控制规则,定义了流量控制的各种参数。
1 | 复制代码public class FlowRule extends AbstractRule { |
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
- resource:资源名,即限流规则的作用对象
- count: 限流阈值
- grade: 限流阈值类型(QPS 或并发线程数)
- limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
- strategy: 调用关系限流策略
- controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
4.2. FlowSlot
FlowSlot是负责流量控制的功能插槽,具体实现如下:
1 | 复制代码 * 流量规则检查器 |
FlowSLot中定义了一个FlowRuleChecker,FlowRuleChecker负责对限流规则进行检查。可以看到,FlowSlot实际的限流逻辑调用FlowRuleChecker实现的。在调用FlowRuleChecker的checkFlow()方法的时候,需要传入一个ruleProvider,这是一个Function。
4.3. FlowRuleChecker
上面说到FlowRuleChecker是负责流量控制校验的,FlowSlot中调用FlowRuleChecker的checkFlow()方法:
1 | 复制代码public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource, |
checkFlow()很简单,就是获取限流规则,遍历调用canPassCheck()方法进行校验,如果校验失败,则抛出FlowException。
1 | 复制代码public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount, |
canPassCheck()也很简单,就是根据不同的模式调用不同的方法。Sentinel可以使用集群模式运行或者本地模式,不同的模式限流逻辑不一样。这个地方由于没有讲到集群,所以先按本地模式进行分析。
1 | 复制代码 private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, |
passLocalCheck()中,先根据配置的strategy和请求来源,获得对应的Node节点,再调用对应节点的canPass()方法进行校验。
在FlowRule中有一个TrafficShapingController,代表流量控制的类型,包括默认值、直接拒绝、Warm Up、匀速排队,所以具体如何限流需要看设置的策略。
4.4. TrafficShapingController
TrafficShapingController是一个接口,主要是定义流量控制策略,它有三个实现,分别代表不同的处理模式:
- DefaultController:默认处理策略,直接拒绝处理
- RateLimiterController:匀速排队
- WarmUpController:预热/冷启动方式
- WarmUpRateLimiterController:预热+匀速排队
在FlowRule中会有一个TrafficShapingController类型的变量rater,这个rater是在更新规则的时候根据设置的规则创建出来的,具体如下:
1 | 复制代码private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) { |
5. 熔断降级
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态的时候对该资源进行限制,让请求快速失败,避免影响其他的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口内,对该资源的调用都会自动熔断。
5.1. 降级策略
- 平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
- 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
- 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态
5.2. DegradeRule
1 | 复制代码public class DegradeRule extends AbstractRule { |
DeGradeRule代表熔断降级的规则,各个字段含义为:
- grade:熔断降级的模式,有平均响应时间、异常比例、异常数。
- count:发生熔断降级的阈值。
- timeWindow:发生熔断降级后持续的时间。
- minRequestAmount:每秒连续进入的请求发生异常不熔断降级的最小阈值。
- rtSlowRequestAmount:每秒连续进入的请求平均响应时间超过阈值的数量。
5.3. DegradeSlot
DegradeSlot是负责处理熔断降级的功能插槽,其代码非常简单,因为具体熔断降级的判断是在DegradeRuleManager中实现的。
1 | 复制代码public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) |
5.4. DegradeRuleManager
DegradeRuleManager主要负责加载熔断降级规则、对调用entry进行熔断降级校验。
1 | 复制代码/** |
DegradeRuleManager使用一个map来保存不同资源加载的熔断降级规则,并且也拥有自己的规则改变处理器,规则的加载和更新逻辑与其他功能插槽类似。
从上面可以看出,当entry进入DegradeSlot的时候,实际上是调用DegradeRuleManager的checkDegrade()方法进行熔断降级的检查。
1 | 复制代码 public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count) |
checkDegrade()方法先根据resource获取对应的限流规则,然后循环调用规则的passCheck()方法进行检查,如果不能通过检查,则抛出DegradeException。
所以,具体的校验逻辑是在DegradeRule中的passCheck()实现的,具体代码如下:
1 | 复制代码 public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) { |
passCheck()逻辑比较简单,主要是先根据之前获取的数据判断是否满足降级设置的阈值,如果超过,则返回false,并且开启一个线程,其作用就是发生降级后指定的timeWindow内直接将后续请求当做降级处理。
6. 小结
这篇文章主要分析Sentinel中常用的功能插槽的实现原理,了解Sentinel的限流降级策略。
7.参考资料
本文转载自: 掘金