第23课:Sentinel熔断降级、系统自适应保护、集群限流

文章目录

适配版本 :Sentinel 1.8.9、Spring Cloud Alibaba 2025.1.0.0、Spring Cloud 2025.1.3(Oakwood)、Spring Boot 4.0.8、JDK 21

课程定位:熔断限流容错阶段进阶,从熔断三种策略到系统自适应保护,从状态机源码到集群限流部署,构建完整的容错闭环

一、开篇:流控之后,还需要熔断

第22课我们完成了Sentinel流控的全方位实战:直接/关联/链路三种模式、快速失败/Warm Up/排队等待三种效果、热点参数限流、规则持久化到Nacos。流控解决的是"流量太大"的问题------当请求量超过阈值时,主动拒绝或排队,保护系统不被过载压垮。

但流控有一个前提假设:被限流的资源本身是健康的 。如果下游服务已经出现故障(如响应时间从50ms飙升到5秒),继续向它发送请求,即使QPS在阈值以内,也会导致线程被阻塞、调用链被拖垮。这时候需要的是熔断------检测到下游服务异常后,主动切断调用,让上游服务快速失败或返回降级响应。

熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。熔断器的工作模式是三个状态的循环:关闭 → 打开 → 半开 → 关闭。关闭状态下正常放行;打开状态下所有请求立即失败(不发送到下游);半开状态下放行少量请求探测下游是否恢复。

本课将从熔断的三种策略讲起,深入DegradeSlot的源码和断路器状态机,实战慢调用比例、异常比例、异常数三种熔断规则的配置,然后讲解系统自适应保护的五种阈值类型,最后完成集群限流的Token Server部署。

二、熔断规则核心参数解析

2.1 DegradeRule的完整字段

熔断降级规则由DegradeRule类定义,包含以下核心字段:

java 复制代码
public class DegradeRule extends AbstractRule {
    private int grade;                  // 熔断策略类型
    private double count;               // 阈值(含义取决于grade)
    private int timeWindow;             // 熔断后恢复时间(秒)
    private int minRequestAmount;       // 最小请求数(防止统计噪声)
    private double slowRatioThreshold;  // 慢请求比例阈值(仅RT模式)
    private int statIntervalMs;         // 统计窗口长度(毫秒,默认1000ms)
}

2.2 三种熔断策略对照

策略 grade值 count含义 触发条件 适用场景
慢调用比例 0(DEGRADE_GRADE_RT) 慢调用临界RT(毫秒) 统计窗口内请求数≥minRequestAmount,且慢调用比例≥slowRatioThreshold 下游服务响应变慢
异常比例 1(DEGRADE_GRADE_EXCEPTION_RATIO) 异常比例阈值(0.0~1.0) 统计窗口内请求数≥minRequestAmount,且异常比例≥count 下游服务异常率升高
异常数 2(DEGRADE_GRADE_EXCEPTION_COUNT) 每秒异常数阈值 统计窗口内异常数≥count 快速失败,对异常数敏感

关键理解 :从Sentinel 1.8开始,RT模式不再看"平均RT",而是看"慢请求比例 "------统计窗口内,响应时间超过count的请求占比超过slowRatioThreshold时才触发熔断。这比1.7之前的"连续N个请求都超时"更科学,避免了因个别慢请求导致的误熔断。

三、慢调用比例熔断实战

3.1 配置示例

java 复制代码
@Configuration
public class DegradeRuleConfig {

    @PostConstruct
    public void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();

        // 慢调用比例熔断
        DegradeRule slowRule = new DegradeRule("callPaymentService")
            .setGrade(RuleConstant.DEGRADE_GRADE_RT)       // 慢调用比例模式
            .setCount(1000)                                 // 慢调用RT阈值:1000ms
            .setSlowRatioThreshold(0.5)                     // 慢调用比例阈值:50%
            .setMinRequestAmount(5)                         // 最小请求数:5
            .setTimeWindow(10)                              // 熔断时长:10秒
            .setStatIntervalMs(10000);                      // 统计窗口:10秒

        rules.add(slowRule);
        DegradeRuleManager.loadRules(rules);
    }
}

配置解析:

参数 值 含义
count 1000 响应时间超过1000ms的请求记为"慢调用"
slowRatioThreshold 0.5 慢调用比例超过50%触发熔断
minRequestAmount 5 统计窗口内至少有5个请求才做判断
timeWindow 10 熔断后10秒内请求快速失败
statIntervalMs 10000 统计窗口为10秒

3.2 熔断触发条件

触发熔断的完整条件(三个条件同时满足):

  1. 统计窗口(statIntervalMs)内,总请求数 ≥ minRequestAmount
  2. 慢调用(RT > count)的比例 ≥ slowRatioThreshold
  3. 当前断路器状态为CLOSED

3.3 熔断后的行为

熔断触发后,断路器从CLOSED变为OPEN:

  • OPEN状态 :timeWindow秒内,所有请求被立即拒绝,抛出DegradeException
  • HALF_OPEN状态 :timeWindow到期后,断路器进入半开状态,放行一个探测请求
    • 如果探测请求的RT < count(不是慢调用),断路器恢复到CLOSED
    • 如果探测请求仍然是慢调用,断路器重新回到OPEN,重新计时

3.4 生产配置建议

场景 count(RT阈值) slowRatioThreshold timeWindow
支付/订单等核心服务 800ms 0.3 30秒
商品查询等一般服务 500ms 0.5 10秒
日志上报等非核心服务 2000ms 0.8 5秒

黄金法则 :count应设置为下游服务P99响应时间的1.5~2倍 。如果下游P99是300ms,count设为500-600ms。设置过低会导致正常的慢请求被误判,设置过高则失去熔断的意义。

四、异常比例与异常数熔断实战

4.1 异常比例熔断

java 复制代码
DegradeRule exceptionRatioRule = new DegradeRule("callUserService")
    .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO)  // 异常比例模式
    .setCount(0.5)                                          // 异常比例阈值:50%
    .setMinRequestAmount(10)                                // 最小请求数:10
    .setTimeWindow(30)                                      // 熔断时长:30秒
    .setStatIntervalMs(10000);                              // 统计窗口:10秒

触发条件:统计窗口内请求数 ≥ 10,且异常比例 ≥ 50%。

异常比例的统计范围 :异常降级仅针对业务异常 ,对Sentinel限流降级本身的异常(BlockException)不生效。需要通过Tracer.trace(ex)记录业务异常,或使用@SentinelResource注解(会自动统计)。

4.2 异常数熔断

java 复制代码
DegradeRule exceptionCountRule = new DegradeRule("callInventoryService")
    .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT)  // 异常数模式
    .setCount(20)                                           // 异常数阈值:20
    .setTimeWindow(60)                                      // 熔断时长:60秒
    .setStatIntervalMs(60000);                              // 统计窗口:60秒

触发条件:统计窗口内异常数 ≥ 20。

异常比例 vs 异常数 :异常比例适用于请求量大且相对稳定 的场景(比例更有统计意义);异常数适用于请求量小或波动大的场景(比例容易被少数请求扭曲)。

4.3 三种策略组合使用

同一个资源可以配置多条熔断规则,任意一条触发即熔断:

java 复制代码
List<DegradeRule> rules = new ArrayList<>();
rules.add(new DegradeRule("callUserService")
        .setGrade(RuleConstant.DEGRADE_GRADE_RT)
        .setCount(500).setSlowRatioThreshold(0.6)
        .setMinRequestAmount(10).setTimeWindow(30));
rules.add(new DegradeRule("callUserService")
        .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO)
        .setCount(0.5).setMinRequestAmount(10).setTimeWindow(30));
rules.add(new DegradeRule("callUserService")
        .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT)
        .setCount(30).setTimeWindow(30));
DegradeRuleManager.loadRules(rules);

五、断路器状态机与DegradeSlot源码解析

5.1 两种断路器实现

Sentinel的断路器分为两类:

断路器类 适用策略 统计维度
ResponseTimeCircuitBreaker 慢调用比例 慢调用数、总请求数
ExceptionCircuitBreaker 异常比例、异常数 异常数、总请求数

5.2 DegradeSlot的entry与exit

DegradeSlot是ProcessorSlotChain的最后一环,负责熔断规则的校验:

java 复制代码
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper,
                      DefaultNode node, int count, boolean prioritized, Object... args) {
        // 执行熔断检查
        performChecking(context, resourceWrapper);
        // 继续执行(实际上已无下一个Slot)
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    private void performChecking(Context context, ResourceWrapper r) throws BlockException {
        List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
        if (circuitBreakers == null || circuitBreakers.isEmpty()) return;

        for (CircuitBreaker cb : circuitBreakers) {
            if (!cb.tryPass(context)) {
                throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
            }
        }
    }
}

执行时机 :DegradeSlot.entry()在请求进入时 执行,检查断路器是否允许通过。DegradeSlot.exit()在请求完成时 执行,触发circuitBreaker.onRequestComplete()回调,更新统计指标并判断是否触发状态转换。

5.3 tryPass的核心逻辑

java 复制代码
public boolean tryPass(Context context) {
    // 1. 当前状态为CLOSED → 允许通过
    if (currentState.get() == State.CLOSED) {
        return true;
    }
    // 2. 当前状态为OPEN → 检查是否已过熔断时长
    if (currentState.get() == State.OPEN) {
        // 熔断时长已到 → 尝试转换为HALF_OPEN
        if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {
            // 成功转换,放行探测请求
            return true;
        }
        // 熔断时长未到 → 拒绝
        return false;
    }
    // 3. 当前状态为HALF_OPEN
    // 只放行一个探测请求,其他请求拒绝
    return currentState.compareAndSet(State.HALF_OPEN, State.HALF_OPEN);
}

5.4 状态转换的触发时机

状态转换 触发条件 执行位置
CLOSED → OPEN 统计窗口内慢调用/异常比例超过阈值 onRequestComplete()
OPEN → HALF_OPEN timeWindow到期 tryPass()
HALF_OPEN → CLOSED 探测请求成功 onRequestComplete()
HALF_OPEN → OPEN 探测请求失败(慢调用或异常) onRequestComplete()

5.5 熔断器状态机图示

复制代码
┌──────────┐
│  CLOSED  │  正常状态,所有请求通过
│  (关闭)  │  统计慢调用/异常指标
└────┬─────┘
     │ 触发熔断条件
     │ (慢调用比例 ≥ slowRatioThreshold
     │  或 异常比例 ≥ count
     │  或 异常数 ≥ count)
     ▼
┌──────────┐
│   OPEN   │  熔断状态,所有请求被拒绝
│  (打开)  │  返回 DegradeException
└────┬─────┘
     │ timeWindow 到期
     ▼
┌──────────┐
│HALF_OPEN │  半开状态,放行一个探测请求
│  (半开)  │
└────┬─────┘
     │
     ├─── 探测请求成功(RT < count 且无异常)
     │    → 恢复 CLOSED
     │
     └─── 探测请求失败(慢调用或异常)
          → 重新 OPEN,重新计时

六、系统自适应保护

6.1 核心理念

Sentinel系统自适应保护从整体维度对应用入口流量进行控制,结合应用的Load、总体平均RT、入口QPS和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡。

传统方案根据load1阈值来决定是否限流,存在两个问题:负载是"果"而非"因",调整通过率后需要等待一段时间才能看到效果;下游恢复后负载仍然很高,恢复缓慢。Sentinel借鉴了TCP BBR的思想:用load1作为启动控制流量的值,而允许通过的流量由处理请求的能力(响应时间、当前QPS)来决定。

6.2 五种系统规则阈值类型

类型 触发条件 说明
Load load1 > 阈值 且 并发线程数 > 系统容量 仅对Linux/Unix生效,系统容量 = maxQps × minRt
CPU使用率 CPU使用率 > 阈值 取值范围0.0~1.0
平均RT 所有入口流量的平均RT > 阈值 单位毫秒
并发线程数 所有入口流量的并发线程数 > 阈值 全局维度
入口QPS 所有入口流量的QPS > 阈值 全局维度

重要限制 :系统规则是应用整体维度 的,不是资源维度的,且仅对入口流量(EntryType.IN)生效。

6.3 配置示例

java 复制代码
@Configuration
public class SystemRuleConfig {

    @PostConstruct
    public void initSystemRules() {
        List<SystemRule> rules = new ArrayList<>();

        // CPU使用率超过80%触发系统保护
        SystemRule cpuRule = new SystemRule();
        cpuRule.setHighestCpuUsage(0.8);
        rules.add(cpuRule);

        // 入口QPS超过10000触发系统保护
        SystemRule qpsRule = new SystemRule();
        qpsRule.setQps(10000);
        rules.add(qpsRule);

        // 并发线程数超过500触发系统保护
        SystemRule threadRule = new SystemRule();
        threadRule.setMaxThread(500);
        rules.add(threadRule);

        SystemRuleManager.loadRules(rules);
    }
}

6.4 Load保护的特殊逻辑

Load保护的触发条件较为特殊,需要同时满足两个条件:

  1. 当前系统load1 > highestSystemLoad阈值
  2. 当前并发请求数 > 系统容量(系统容量 = maxQps × minRt)

为什么需要两个条件? 仅凭load1判断会误伤------load1高可能是由于I/O等待而非CPU竞争。加上并发数的判断后,只有在"系统确实处理不过来"时才触发保护,避免了对正常高负载的误判。

Load阈值设定参考值 :CPU cores × 2.5。例如4核机器,Load阈值设为10。

七、集群限流

7.1 为什么需要集群限流

单机限流存在一个根本问题:流量分配不均匀。假设集群有10台机器,每台限流10 QPS,理论上集群总量100 QPS。但实际流量可能不均匀------某些机器收到15 QPS被限流,而其他机器只收到5 QPS,导致"总量没到,但部分机器已经限流"。

集群限流可以精确控制整个集群的调用总量,确保不管流量如何分布,总QPS始终不超过预设阈值。

7.2 Token Server与Token Client

集群流控中有两种身份:

身份 职责
Token Server 集群流控服务端,处理来自Token Client的请求,根据规则判断是否发放令牌
Token Client 集群流控客户端,向Token Server申请令牌,根据返回结果决定是否限流

通信机制:Token Client向Token Server发送令牌申请请求,Token Server根据当前集群的QPS统计和规则阈值,判断是否发放令牌。如果发放,Client放行请求;如果不发放,Client执行本地限流逻辑。

7.3 集群流控规则配置

FlowRule添加集群模式:

java 复制代码
ClusterFlowConfig clusterConfig = new ClusterFlowConfig()
    .setFlowId(1L)                              // 全局唯一的规则ID
    .setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL)  // 全局阈值模式
    .setFallbackToLocalWhenFail(true);          // Token Server不可用时回退到本地限流

FlowRule rule = new FlowRule("cluster-resource")
    .setGrade(RuleConstant.FLOW_GRADE_QPS)
    .setCount(1000)                             // 集群总QPS阈值
    .setClusterMode(true)
    .setClusterConfig(clusterConfig);

FlowRuleManager.loadRules(Collections.singletonList(rule));

关键字段说明:

字段 含义 说明
flowId 全局唯一规则ID 由管控端分配,保持全局唯一
thresholdType 阈值模式 0=单机均摊,1=全局阈值
fallbackToLocalWhenFail 回退策略 Token Server不可用时是否回退到本地限流

单机均摊 vs 全局阈值:单机均摊模式下,配置的阈值等同于单机承受限额,Token Server根据连接数计算总阈值(3个Client连接,单机阈值10,总阈值30);全局模式下,配置的阈值就是整个集群的总阈值。

7.4 Token Server部署方式

方式一:独立模式。单独部署一个Token Server进程,所有Token Client连接到它:

java 复制代码
ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
tokenServer.start();

方式二:嵌入模式。集群中选择一个节点同时作为Token Server和Token Client:

java 复制代码
// 在应用启动时,将当前节点嵌入为Token Server
ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
tokenServer.start();
// 同时将当前节点作为Token Client
ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT);

生产建议 :独立模式更稳定(Token Server故障不影响业务节点),但需要额外的部署资源;嵌入模式节省资源,但Token Server节点的故障会影响整个集群的限流功能。推荐使用独立模式,并通过Nacos实现Token Server的自动选举和故障转移。

7.5 Token Server高可用方案

Token Server本身如果成为单点,整个集群限流功能就会失效。高可用方案如下:

方案一:Nacos自主选举。借助Nacos注册中心,集群中所有节点注册到Nacos,选取注册列表中第一个节点作为Token Server。如果Token Server不可用,重新选举。

方案二:Sentinel Dashboard指定。通过Sentinel Dashboard手动指定某个节点为Token Server,其他节点作为Token Client。Dashboard提供了集群流控的配置界面。

方案三:Fallback保护 。配置fallbackToLocalWhenFail=true,当Token Client无法连接Token Server时,自动回退到本地单机限流模式,保证服务不会因Token Server故障而完全不可用。

八、踩坑指南

坑一:熔断规则count配反了

现象 :慢调用比例模式下,count设为0.5,但期望的是RT阈值500ms。

原因 :混淆了count和slowRatioThreshold。慢调用比例模式下,count是RT阈值 (毫秒),slowRatioThreshold才是慢调用比例阈值(0.0~1.0)。

解决 :setCount(500)设置RT阈值,setSlowRatioThreshold(0.5)设置比例阈值。

坑二:熔断后没有降级响应

现象 :熔断触发后,返回的是Sentinel默认的DegradeException,前端无法处理。

原因 :未配置blockHandler或BlockExceptionHandler。

解决 :使用@SentinelResource的blockHandler指定降级方法,或实现BlockExceptionHandler接口统一处理(第22课6.2节)。

坑三:熔断恢复后立即再次熔断

现象:熔断时长到期后,第一个探测请求通过,但随后立即再次熔断。

原因:下游服务的问题没有真正恢复,探测请求仍然是慢调用或异常。

解决 :这是正常行为------熔断器的半开状态本身就是"试探性恢复"。应检查下游服务是否真正恢复健康,或适当增加timeWindow给下游更长的恢复时间。

坑四:系统自适应保护的Load阈值不生效

现象:配置了Load保护规则,但系统load1已经很高,仍然没有触发限流。

原因 :Load保护需要同时满足"load1 > 阈值"和"并发线程数 > 系统容量"两个条件。如果并发数不够高,即使load1很高也不会触发。

解决 :检查并发线程数是否超过maxQps × minRt。如果并发数不足,Load保护不会生效------这是设计行为,避免对正常高负载的误判。

坑五:集群限流Token Server不可用导致全局限流失效

现象:Token Server宕机后,所有Token Client的集群限流完全失效,流量全部放行。

原因 :未配置fallbackToLocalWhenFail=true。

解决 :始终设置fallbackToLocalWhenFail=true,确保Token Server不可用时自动回退到本地单机限流,保证基本的保护能力。

坑六:集群限流规则ID不唯一导致规则冲突

现象 :多个集群限流规则配置了相同的flowId,导致限流行为异常。

原因 :flowId是全局唯一的,不能重复。

解决 :由管控端统一分配flowId,或使用数据库自增ID确保唯一性。

九、课后作业

作业一 :为callUserService资源配置慢调用比例熔断:RT阈值800ms,慢调用比例阈值50%,最小请求数5,熔断时长15秒。模拟下游服务响应缓慢(Thread.sleep(1000)),验证熔断触发和恢复。

作业二 :为callPaymentService资源配置异常比例熔断:异常比例阈值40%,最小请求数10,熔断时长30秒。通过人为抛出异常验证熔断触发,观察异常比例从上升到达标再到熔断的完整过程。

作业三:配置系统自适应保护规则,CPU使用率阈值0.8,入口QPS阈值5000,并发线程数阈值300。使用压测工具施加压力,观察哪个维度的阈值最先触发。

作业四 :部署Token Server(独立模式),配置集群限流规则(全局阈值模式,QPS=1000)。启动两个Token Client实例,验证集群总QPS精确控制在1000以内。然后停止Token Server,验证fallbackToLocalWhenFail回退机制。

作业五(进阶) :阅读DegradeSlot和ResponseTimeCircuitBreaker源码,画出慢调用比例熔断的完整状态转换图,标注每个状态转换的触发条件和执行方法。

十、下节预告

第24课将进入Sentinel网关限流、授权规则 & 生产级容错方案。内容包括Gateway网关专属限流(Sentinel集成Gateway)、黑白名单授权规则、IP限流、用户维度限流、规则Nacos持久化的完整实战,以及生产环境的故障演练和容错体系搭建。本课完成了Sentinel的熔断降级和系统保护,第24课将把Sentinel的能力扩展到网关层,并构建完整的生产级容错体系。

🔗《最新版 SpringCloud 2025 从入门到实战》系列课程导航

去订阅

第一部分:微服务前置基础 & 新版环境搭建(第1-5课)

第二部分:注册中心核心(Nacos 最新版)(第6-9课)

第三部分:配置中心核心(Nacos配置中心)(第10-12课)

第四部分:服务通信核心(OpenFeign + LoadBalancer)(第13-16课)

第五部分:网关核心(SpringCloud Gateway 新版)(第17-20课)

第六部分:熔断、限流、降级(Sentinel 新版)(第21-24课)

第七部分:微服务监控、链路追踪、日志体系(第25-28课)

第八部分:微服务高阶特性 & 分布式核心能力(第29-31课)

第九部分:企业级完整项目实战 & 架构复盘(第32-35课)

相关推荐
2501_933923251 天前
SpringCloud组件:Eureka服务注册与发现
java·开发语言·后端·spring cloud·eureka
叶总没有会1 天前
Gateway--网关
spring cloud·微服务·gateway
jyOverQ1 天前
Redis 主从复制、Sentinel 和 Cluster 到底有什么区别?
java·redis·sentinel
海马2 天前
Spring Cloud 开发知识整理
spring·spring cloud·wpf
栗子~~2 天前
SpringCloud Gateway 基于 Nacos 实现动态路由
java·spring cloud·gateway
code_slave(码畜)2 天前
微服务架构落地:消息队列架构设计(上篇)——异步解耦、削峰填谷,看懂业务事件流转本质
spring boot·spring cloud·微服务·云原生·架构
code_slave(码畜)2 天前
微服务架构落地:消息队列架构设计(下篇)——消息堆积、死信治理与集群监控告警实战
spring boot·spring cloud·微服务·云原生·中间件·架构
知守观2 天前
feign-core 就在依赖树里,运行时却找不到类:一次 provided scope 引发的启动失败排查
java·spring cloud·maven
阿狗童鞋2 天前
高并发系统设计实战指南
java·spring boot·spring cloud