SpringNotes

第 20 章:熔断限流与降级

zjc 于 2026-01-20 发布

这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 分布式系统中,故障不可避免。弹性的目标是让局部故障不演变成全局雪崩,并在资源不足时保护核心链路。熔断、限流、隔离和降级是四种互补手段。

20.1 故障传播

下游变慢
  -> 调用线程阻塞
  -> 线程池耗尽
  -> 上游无法处理其他请求
  -> 更多服务级联失败

保护顺序:

超时 -> 限流 -> 并发隔离 -> 熔断 -> 降级 -> 恢复

没有超时的重试和熔断都可能加剧故障。

20.2 Resilience4j

依赖:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>

配置:

resilience4j:
  circuitbreaker:
    instances:
      inventory:
        sliding-window-size: 100
        minimum-number-of-calls: 20
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 5

使用:

@Service
public class InventoryClient {

    @CircuitBreaker(name = "inventory", fallbackMethod = "fallback")
    public InventoryView get(Long skuId) {
        return restClient.get().uri("/api/inventories/{id}", skuId)
                .retrieve()
                .body(InventoryView.class);
    }

    private InventoryView fallback(Long skuId, Throwable cause) {
        return InventoryView.unknown(skuId);
    }
}

20.3 熔断器状态

CLOSED
  -> 失败率达到阈值
OPEN
  -> 等待冷却
HALF_OPEN
  -> 放少量探测请求
  -> 成功恢复 CLOSED
  -> 失败回到 OPEN

参数:

参数 说明
sliding window 统计窗口
minimum calls 最小调用数
failure rate 失败率阈值
slow call rate 慢调用比例
wait duration 打开时长
half-open calls 探测请求数

不要只按失败率熔断,慢调用往往是雪崩前兆。

20.4 限流

resilience4j:
  ratelimiter:
    instances:
      createOrder:
        limit-for-period: 100
        limit-refresh-period: 1s
        timeout-duration: 0
@RateLimiter(name = "createOrder")
public Order create(CreateOrderCommand command) {
    return orderService.create(command);
}

限流位置:

位置 特点
网关 全局和用户级
应用 业务细粒度
数据库 保护连接和 SQL
下游客户端 保护依赖

被限流后可返回:

  1. 429;
  2. 排队等待;
  3. 降级结果;
  4. 优先级策略;
  5. 异步任务。

20.5 并发隔离

信号量隔离:

@Bulkhead(name = "inventory", type = Bulkhead.Type.SEMAPHORE)
public InventoryView get(Long id) {
}

线程池隔离:

inventory-pool
pay-pool
user-pool

对比:

方式 优点 缺点
信号量 轻量、无线程切换 不能设置异步超时
线程池 隔离强、可超时 线程成本高

阻塞 IO 常用线程池隔离;Reactive 或轻量保护可用信号量。

20.6 降级设计

降级策略:

场景 策略
推荐服务不可用 返回默认推荐
评论服务不可用 隐藏评论区
库存查询失败 显示“暂不可购买”
优惠券失败 不优惠并提示
支付失败 引导重试
搜索失败 返回历史热门

设计原则:

  1. 核心交易优先;
  2. 降级结果业务可接受;
  3. 数据不能错账;
  4. 用户明确感知;
  5. 降级有开关和审计;
  6. 恢复后自动或人工切回。

20.7 重试与熔断组合

顺序建议:

RateLimiter
  -> CircuitBreaker
     -> Retry
        -> TimeLimiter
           -> 实际调用

常见错误:

  1. 熔断已打开仍重试;
  2. 非幂等 POST 重试;
  3. 重试无预算;
  4. fallback 吞掉异常导致无监控;
  5. fallback 又调用不可用下游;
  6. 所有服务共享一个熔断器。

fallback 必须记录:

private InventoryView fallback(Long id, Throwable cause) {
    log.warn("inventory fallback, id={}, type={}", id, cause.getClass().getSimpleName());
    return InventoryView.unknown(id);
}

20.8 Sentinel 选择

Sentinel 提供流量控制、熔断、热点限流、系统自适应保护和控制台。适合需要集中规则管理和实时调整的场景。

依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: sentinel.example.com:8080

与 Resilience4j 对比:

维度 Resilience4j Sentinel
风格 轻量库 库 + 控制台生态
规则 配置文件和代码 支持动态规则
热点 需要自行扩展 内置支持
适用 Spring Cloud 标准 Alibaba 生态常见

按团队平台选择,不叠加两套复杂体系。

20.9 指标与演练

指标:

circuit_state
circuit_failure_rate
circuit_slow_call_rate
circuit_calls_total
ratelimit_available_permits
ratelimit_rejected_total
bulkhead_available
fallback_total
downstream_latency

告警:

  1. 熔断打开;
  2. 限流拒绝突增;
  3. fallback 突增;
  4. 线程池排队;
  5. 下游 P99 超标。

演练场景:

  1. 下游延迟 5 秒;
  2. 下游返回 500;
  3. 数据库连接耗尽;
  4. 缓存失效;
  5. 实例减半;
  6. 网络抖动。

验证服务是否按预期降级,而不是直接线程池耗尽。

本章小结

弹性治理通过超时、限流、隔离、熔断和降级防止故障扩散。Resilience4j 适合轻量集成,Sentinel 适合需要控制台和动态规则的生态。降级必须符合业务语义,重试必须幂等且有预算,所有保护动作都要有指标、日志和演练验证。

思考题

  1. 为什么说没有超时的重试是危险的?
  2. 熔断 OPEN 状态的作用是什么?
  3. 信号量隔离和线程池隔离如何选择?
  4. fallback 中为什么必须记录日志?
  5. 如何设计一次雪崩演练?