1.4 系统自适应保护

    科技2025-07-16  41

    目录

    Pt1 系统保护思路

    Pt2 系统保护规则

    Pt3 系统保护原理

    Pt4 系统保护示例


    Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

    针对这个情况,Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

     

    Pt1 系统保护思路

    在开始之前,我们先了解一下系统保护的目的:

    保证系统不被拖垮

    在系统稳定的前提下,保持系统的吞吐量

     

    长期以来,系统保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:

    load 是一个“结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。

    恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。

    TCP BBR 的思想给了我们一个很大的启发。我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终我们追求的目标是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值。如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。

    Sentinel 在系统自适应保护的做法是,用 load1 作为启动自适应保护的因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。

     

    Pt2 系统保护规则

    系统保护规则是从应用级别的入口流量进行控制,从单台机器的 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 达到阈值即触发系统保护。

     

    Pt3 系统保护原理

     

    我们把系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间。

    推论一: 如果我们能够保证水管里的水量,能够让水顺畅的流动,则不会增加排队的请求;也就是说,这个时候的系统负载不会进一步恶化。

    我们用 T 来表示(水管内部的水量),用RT来表示请求的处理时间,用P来表示进来的请求数,那么一个请求从进入水管道到从水管出来,这个水管会存在 P * RT 个请求。换一句话来说,当 T ≈ QPS * Avg(RT) 的时候,我们可以认为系统的处理能力和允许进入的请求个数达到了平衡,系统的负载不会进一步恶化。

    接下来的问题是,水管的水位是可以达到了一个平衡点,但是这个平衡点只能保证水管的水位不再继续增高,但是还面临一个问题,就是在达到平衡点之前,这个水管里已经堆积了多少水。如果之前水管的水已经在一个量级了,那么这个时候系统允许通过的水量可能只能缓慢通过,RT会大,之前堆积在水管里的水会滞留;反之,如果之前的水管水位偏低,那么又会浪费了系统的处理能力。

    推论二: 当保持入口的流量是水管出来的流量的最大的值的时候,可以最大利用水管的处理能力。

    然而,和 TCP BBR 的不一样的地方在于,还需要用一个系统负载的值(load1)来激发这套机制启动。

    注:这种系统自适应算法对于低 load 的请求,它的效果是一个“兜底”的角色。对于不是应用本身造成的 load 高的情况(如其它进程导致的不稳定的情况),效果不明显。

     

    Pt4 系统保护示例

    // 1.加载Sentinel依赖 <!-- Sentinel --> <dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>    <version>2.1.0.RELEASE</version> </dependency> // 2.加载限流规则(系统保护) @SpringBootApplication public class MicroserviceApplication { ​    public static void main(String[] args) {        // 启动时将规则加载到内存        initFlowRule();        initSystemRule(); ​        SpringApplication.run(MicroserviceApplication.class, args);   } ​    // 初始化限流规则    private static void initSystemRule() {        List<SystemRule> rules = new ArrayList<SystemRule>();        SystemRule rule = new SystemRule();        // max load is 3        rule.setHighestSystemLoad(3.0);        // max cpu usage is 60%        rule.setHighestCpuUsage(0.3);        // max avg rt of all request is 10 ms        rule.setAvgRt(10);        // max total qps is 20        rule.setQps(2);        // max parallel working thread is 10        rule.setMaxThread(5); ​        rules.add(rule);        SystemRuleManager.loadRules(Collections.singletonList(rule));   } } // 请求入口 @RestController public class SentinelController { ​    @Autowired    public SentinelService sentinelService; ​    @RequestMapping("/hello")    public String hello() {            sentinelService.hello();        return "hello world.";   } } // 3.资源定义 @Service("sentinelService") public class SentinelService { ​    public String hello() {        Entry entry = null; ​        try {            // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。            entry = SphU.entry("hello");            // 被保护的业务逻辑            System.out.println("hello world.");       } catch (BlockException ex) {            // 资源访问阻止,被限流或被降级            System.out.println("Request blocked.");       } finally {            if (entry != null) {                entry.exit();           }       } ​        return "hello world.";   } } 快速刷新页面,显示被限流:Blocked by Sentinel (flow limiting)

     

    Processed: 0.012, SQL: 8