Skip to content
第 27 章 架构 ⏱ 12 分钟阅读

第 27 章:熔断与隔离 ​

学习目标 ​

  • 理解熔断与降级的区别
  • 掌握 Resilience4j 实战
  • 学会线程隔离、信号量隔离
  • 避免熔断误判、雪崩的坑

一、熔断 vs 降级 vs 限流 ​

text
限流: 主动控制流量,超出拒绝
降级: 服务异常,返回兜底
熔断: 故障服务自动断开,过一段时间试用
java
// 限流: 1000 QPS → 第 1001 个拒绝
// 降级: 调用 payment 失败 → 返回"支付暂不可用"
// 熔断: payment 失败率 50% → 断开 10s,10s 后试一次

二、熔断器原理 ​

text
状态机
       成功
[CLOSED] ──→ 正常
   │ 失败率高
   ↓
[OPEN] ──→ 熔断,直接走 fallback
   │ 超时
   ↓
[HALF_OPEN] ──→ 试探几个请求
   │ 成功 / 失败
   ↓
回到 CLOSED / OPEN
java
// 关键参数
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: 3s
java
// 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,再等 10s
java
// 手动重置(紧急情况)
@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 / Sentinel
yaml
# 切换 Sentinel
spring:
  cloud:
    circuitbreaker:
      type: sentinel

本章小结 ​

工具特点
Resilience4jSpring 官方,Java 8+
Sentinel阿里,功能全
HystrixNetflix,停更
隔离适用
信号量轻量,IO 密集
线程池耗时,防拖垮
参数建议
failureRateThreshold50%
waitDurationInOpenState10-30s
slidingWindowSize20-100
minimumNumberOfCalls10-20

动手练习 ​

  1. Resilience4j 集成:在订单服务加 paymentService 熔断器
  2. 触发熔断:让支付服务 50% 失败,观察熔断状态
  3. 手动恢复:用 admin API 重置熔断器
  4. 线程隔离:用 ThreadPoolBulkhead 隔离慢调用

下一章:第 28 章:负载均衡 →

本站基于 VitePress 构建 · 由 StackHub 团队维护