第 27 章:熔断与隔离
学习目标
- 理解熔断与降级的区别
- 掌握 Resilience4j 实战
- 学会线程隔离、信号量隔离
- 避免熔断误判、雪崩的坑
一、熔断 vs 降级 vs 限流
text
限流: 主动控制流量,超出拒绝
降级: 服务异常,返回兜底
熔断: 故障服务自动断开,过一段时间试用java
// 限流: 1000 QPS → 第 1001 个拒绝
// 降级: 调用 payment 失败 → 返回"支付暂不可用"
// 熔断: payment 失败率 50% → 断开 10s,10s 后试一次二、熔断器原理
text
状态机
成功
[CLOSED] ──→ 正常
│ 失败率高
↓
[OPEN] ──→ 熔断,直接走 fallback
│ 超时
↓
[HALF_OPEN] ──→ 试探几个请求
│ 成功 / 失败
↓
回到 CLOSED / OPENjava
// 关键参数
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失败率 50% 触发
.waitDurationInOpenState(Duration.ofSeconds(10)) // 断开 10s
.slidingWindowSize(10) // 滑动窗口 10 个请求
.minimumNumberOfCalls(5) // 至少 5 个请求才统计
.permittedNumberOfCallsInHalfOpenState(3) // 半开允许 3 个试探
.build();⚠️ 坑 1:
failureRateThreshold=10太敏感,1 次失败就熔断。至少 20 个请求、失败率 50% 才触发。
三、Resilience4j 实战
xml
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>yaml
resilience4j:
circuitbreaker:
instances:
paymentService:
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
sliding-window-size: 100
minimum-number-of-calls: 20
retry:
instances:
paymentService:
max-attempts: 3
wait-duration: 1s
timelimiter:
instances:
paymentService:
timeout-duration: 3sjava
// 1. 注解方式
@Service
public class OrderService {
@CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
@Retry(name = "paymentService")
@TimeLimiter(name = "paymentService")
public CompletableFuture<PaymentResult> pay(PaymentRequest req) {
return CompletableFuture.supplyAsync(() -> paymentClient.pay(req));
}
public CompletableFuture<PaymentResult> paymentFallback(PaymentRequest req, Throwable e) {
log.warn("支付失败,fallback:{}", e.getMessage());
return CompletableFuture.completedFuture(PaymentResult.pending());
}
}java
// 2. 编程方式
CircuitBreaker breaker = CircuitBreaker.of("paymentService", config);
Supplier<PaymentResult> decorated = Decorators.ofSupplier(() -> paymentClient.pay(req))
.withCircuitBreaker(breaker)
.withRetry(Retry.ofDefaults("paymentService"))
.withFallback(List.of(CallNotPermittedException.class, TimeoutException.class),
e -> PaymentResult.pending())
.decorate();
PaymentResult result = decorated.get();四、线程隔离
java
// 信号量隔离:共享线程,Context 传递
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(20) // 最大并发
.maxWaitDuration(Duration.ofMillis(500)) // 等待时间
.build();
Bulkhead bulkhead = Bulkhead.of("paymentService", config);
// 线程池隔离:独立线程,Context 丢失
ThreadPoolBulkheadConfig threadConfig = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(20)
.coreThreadPoolSize(10)
.queueCapacity(100)
.build();text
信号量 vs 线程池
├── 信号量: 轻量,适合 IO 密集
└── 线程池: 重量,适合耗时操作,防止拖垮主线程java
// 实战:订单服务调用支付 + 库存
@CircuitBreaker(name = "payment", fallbackMethod = "paymentFallback")
public PaymentResult pay(PaymentRequest req) {
return paymentClient.pay(req);
}
@CircuitBreaker(name = "inventory", fallbackMethod = "inventoryFallback")
public Boolean deductInventory(SkuId skuId, int qty) {
return inventoryClient.deduct(skuId, qty);
}⚠️ 坑 2:
pay是慢调用,同步阻塞调用方线程。用ThreadPoolBulkhead异步隔离,调用方立即返回。
五、熔断监控
java
// 监听熔断事件
@EventListener
public void onCircuitBreakerEvent(CircuitBreakerEvent event) {
log.info("CircuitBreaker {}: {}", event.getCircuitBreakerName(), event.getEventType());
// 上报指标
Metrics.counter("circuitbreaker.events",
"name", event.getCircuitBreakerName(),
"type", event.getEventType().name()
).increment();
}yaml
# Prometheus 指标
resilience4j_circuitbreaker_state{name="paymentService",state="closed"} 1
resilience4j_circuitbreaker_failure_rate{name="paymentService"} 0.05
resilience4j_circuitbreaker_calls_total{name="paymentService",kind="successful"} 950六、熔断恢复
java
// 自动恢复:OPEN → HALF_OPEN → CLOSED
// 1. 等待 10s(wait-duration)
// 2. 进入 HALF_OPEN,允许 3 个请求
// 3. 3 个都成功 → CLOSED
// 4. 任一失败 → OPEN,再等 10sjava
// 手动重置(紧急情况)
@PostMapping("/admin/circuit/{name}/reset")
public void reset(@PathVariable String name) {
circuitBreakerRegistry.circuitBreaker(name).reset();
}七、熔断与重试
java
// 重试 + 熔断
@Retry(name = "paymentService", fallbackMethod = "paymentFallback")
@CircuitBreaker(name = "paymentService")
public PaymentResult pay(PaymentRequest req) {
return paymentClient.pay(req);
}
// 顺序很重要:
// 1. 重试在最外层(失败后重试)
// 2. 熔断在内层(连续失败触发熔断)text
订单服务 → 重试 3 次 → 熔断器 → 支付服务
失败 N 次
→ 熔断⚠️ 坑 3:重试放熔断外层,熔断永远不触发(被重试覆盖)。调整顺序:重试外、熔断内。
八、实战:雪崩防护
java
// 场景:订单服务依赖支付 + 库存 + 物流
// 1 个服务慢,导致全部线程阻塞 = 雪崩
// 解法:每个依赖独立隔离 + 熔断
@Service
public class OrderService {
@CircuitBreaker(name = "payment", fallbackMethod = "paymentFb")
public PaymentResult pay() { ... }
@CircuitBreaker(name = "inventory", fallbackMethod = "inventoryFb")
public Boolean deduct() { ... }
@CircuitBreaker(name = "logistics", fallbackMethod = "logisticsFb")
public String arrange() { ... }
}java
// 补偿模式:即使 fallback,也不能数据丢失
public void placeOrder(OrderDto dto) {
PaymentResult p = pay(); // 失败返回兜底
if (p.pending()) {
// 异步重试
retryQueue.add(new PayTask(dto, retryCount));
}
}九、Spring Cloud Circuit Breaker
java
// 统一抽象,支持多实现
@CircuitBreaker(name = "payment", fallbackMethod = "paymentFallback")
public PaymentResult pay(PaymentRequest req) {
return paymentClient.pay(req);
}
// 切换实现:Resilience4j / Hystrix / Sentinelyaml
# 切换 Sentinel
spring:
cloud:
circuitbreaker:
type: sentinel本章小结
| 工具 | 特点 |
|---|---|
| Resilience4j | Spring 官方,Java 8+ |
| Sentinel | 阿里,功能全 |
| Hystrix | Netflix,停更 |
| 隔离 | 适用 |
|---|---|
| 信号量 | 轻量,IO 密集 |
| 线程池 | 耗时,防拖垮 |
| 参数 | 建议 |
|---|---|
| failureRateThreshold | 50% |
| waitDurationInOpenState | 10-30s |
| slidingWindowSize | 20-100 |
| minimumNumberOfCalls | 10-20 |
动手练习
- Resilience4j 集成:在订单服务加
paymentService熔断器 - 触发熔断:让支付服务 50% 失败,观察熔断状态
- 手动恢复:用 admin API 重置熔断器
- 线程隔离:用 ThreadPoolBulkhead 隔离慢调用
下一章:第 28 章:负载均衡 →