这是《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 |
| 下游客户端 | 保护依赖 |
被限流后可返回:
- 429;
- 排队等待;
- 降级结果;
- 优先级策略;
- 异步任务。
20.5 并发隔离
信号量隔离:
@Bulkhead(name = "inventory", type = Bulkhead.Type.SEMAPHORE)
public InventoryView get(Long id) {
}
线程池隔离:
inventory-pool
pay-pool
user-pool
对比:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 信号量 | 轻量、无线程切换 | 不能设置异步超时 |
| 线程池 | 隔离强、可超时 | 线程成本高 |
阻塞 IO 常用线程池隔离;Reactive 或轻量保护可用信号量。
20.6 降级设计
降级策略:
| 场景 | 策略 |
|---|---|
| 推荐服务不可用 | 返回默认推荐 |
| 评论服务不可用 | 隐藏评论区 |
| 库存查询失败 | 显示“暂不可购买” |
| 优惠券失败 | 不优惠并提示 |
| 支付失败 | 引导重试 |
| 搜索失败 | 返回历史热门 |
设计原则:
- 核心交易优先;
- 降级结果业务可接受;
- 数据不能错账;
- 用户明确感知;
- 降级有开关和审计;
- 恢复后自动或人工切回。
20.7 重试与熔断组合
顺序建议:
RateLimiter
-> CircuitBreaker
-> Retry
-> TimeLimiter
-> 实际调用
常见错误:
- 熔断已打开仍重试;
- 非幂等 POST 重试;
- 重试无预算;
- fallback 吞掉异常导致无监控;
- fallback 又调用不可用下游;
- 所有服务共享一个熔断器。
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
告警:
- 熔断打开;
- 限流拒绝突增;
- fallback 突增;
- 线程池排队;
- 下游 P99 超标。
演练场景:
- 下游延迟 5 秒;
- 下游返回 500;
- 数据库连接耗尽;
- 缓存失效;
- 实例减半;
- 网络抖动。
验证服务是否按预期降级,而不是直接线程池耗尽。
本章小结
弹性治理通过超时、限流、隔离、熔断和降级防止故障扩散。Resilience4j 适合轻量集成,Sentinel 适合需要控制台和动态规则的生态。降级必须符合业务语义,重试必须幂等且有预算,所有保护动作都要有指标、日志和演练验证。
思考题
- 为什么说没有超时的重试是危险的?
- 熔断 OPEN 状态的作用是什么?
- 信号量隔离和线程池隔离如何选择?
- fallback 中为什么必须记录日志?
- 如何设计一次雪崩演练?