熔断器解决什么问题
当下游服务故障时,如果不加保护:
- 请求不断超时,线程/连接被占满
- 雪崩效应:故障服务拖垮上游调用方
- 恢复后瞬间涌入的请求再次击垮服务
熔断器(Circuit Breaker)在故障率达到阈值时快速失败,给下游恢复时间。
Resilience4j 熔断器
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
</dependency>
配置与使用
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.reactor.circuitbreaker.operator.CircuitBreakerOperator;
@Configuration
public class ResilienceConfig {
@Bean
public CircuitBreaker orderServiceBreaker() {
io.github.resilience4j.circuitbreaker.CircuitBreakerConfig config =
io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失败率 > 50% 触发熔断
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔断持续 30 秒
.permittedNumberOfCallsInHalfOpenState(10) // 半开时允许 10 个试探请求
.slidingWindowSize(100) // 统计最近 100 次调用
.minimumNumberOfCalls(20) // 至少 20 次调用才开始算失败率
.slowCallRateThreshold(80) // 慢调用 > 80% 也触发熔断
.slowCallDurationThreshold(Duration.ofSeconds(2)) // > 2s 算慢调用
.build();
return CircuitBreaker.of("order-service", config);
}
}
minimumNumberOfCalls 容易被忽略:它的默认值是 100(恰好等于默认窗口大小),低流量服务可能永远凑不够样本,熔断器一直不打开。把它设成 20,表示窗口内至少 20 次调用后才评估失败率。注意它是固定默认值而不是随窗口推导:如果缩小 slidingWindowSize 却没同步调整它,最少调用数会大于窗口容量,窗口永远凑不够评估样本,熔断器等于失效。默认窗口是 COUNT_BASED;改成 TIME_BASED 时 slidingWindowSize 的单位变成秒。maxWaitDurationInHalfOpenState 默认是 0(一直等),如果半开试探请求卡住不返回,熔断器会停在 HALF_OPEN,建议设一个上限(如 5 秒)。
使用:
@Service
public class OrderClient {
private final CircuitBreaker breaker;
private final WebClient webClient;
public Mono<String> getOrders() {
return webClient.get().uri("/api/orders")
.retrieve()
.bodyToMono(String.class)
.transformDeferred(CircuitBreakerOperator.of(breaker))
.onErrorResume(CallNotPermittedException.class, err ->
Mono.just("{\"error\":\"Circuit breaker open\"}"));
}
}
状态机
CLOSED(关闭) OPEN(打开) HALF_OPEN(半开)
│ │ │
│ 失败率 < 50% │ 快速失败 │ 允许有限请求
│ 正常通过 │ 所有请求被拒 │ 检查成功率
│ │ │
│ 失败率 >= 50% │ 等待 30s │ 成功 → CLOSED
└───────────────────→├───────────────────→┤ 失败 → OPEN
半开状态
OPEN 状态等待 30 秒后进入 HALF_OPEN:
- 允许 10 个 试探请求通过
- 这 10 个请求的失败率低于 50%(即成功率高于 50%),回到 CLOSED
- 否则回到 OPEN,再等待 30 秒
这个设计防止服务「刚恢复就被压垮」。
事件监听
breaker.getEventPublisher()
.onStateTransition(event ->
System.out.println("Breaker " + breaker.getName()
+ " transitioned from " + event.getStateTransition().getFromState()
+ " to " + event.getStateTransition().getToState()))
.onError(event ->
System.out.println("Breaker " + breaker.getName() + " recorded error"))
.onSuccess(event ->
System.out.println("Breaker " + breaker.getName() + " recorded success"));
配合 Micrometer(第 10.1 节)暴露指标:
resilience4j_circuitbreaker_state{name="order-service",state="closed"} 1
resilience4j_circuitbreaker_state{name="order-service",state="open"} 0
resilience4j_circuitbreaker_state{name="order-service",state="half_open"} 0
resilience4j_circuitbreaker_calls_total{name="order-service",kind="failed"} 42
resilience4j_circuitbreaker_calls_total{name="order-service",kind="successful"} 158
这些指标来自 resilience4j-micrometer(还需在 CircuitBreakerRegistry 上注册 TaggedCircuitBreakerMetrics),不是引入 circuitbreaker 模块就自动出现。state 是指标标签而不是数值枚举:当前状态对应的序列为 1,其余为 0。
与重试的配合
熔断器和重试的组合顺序很重要:
webClient.get().uri("/api/orders")
.retrieve()
.bodyToMono(String.class)
.transformDeferred(CircuitBreakerOperator.of(breaker)) // 内层:每次真实尝试都计数
.retryWhen(Retry.backoff(2, Duration.ofMillis(100))
.filter(err -> !(err instanceof CallNotPermittedException))) // 外层:重试
.subscribe();
链上的顺序决定谁能看到谁:retryWhen 写在后,它就是外层操作符,每次重试都会重新订阅内层的熔断器,所以熔断器统计的是每一次真实尝试。反过来(retryWhen 在前、熔断器在后),熔断器只会看到整条重试链的最终结果,失败率按「逻辑请求」而不是「尝试次数」计算,触发会更慢(一次「重试两次后成功」在内层记 2 失败 1 成功,在外层只记 1 成功)。
重试必须排除 CallNotPermittedException,否则熔断器已经打开时,重试还会反复发起注定被拒的调用,放大无效流量。
事件驱动服务的特殊考虑
事件驱动架构中,熔断器通常用于同步调用(如 WebClient HTTP 请求)。对于异步消息消费,使用消费者组分区和死信队列代替熔断:
正常队列 → 消费者处理
失败 N 次 → 死信队列(DLQ)
人工/自动重试 DLQ
小结
| 参数 | 含义 | 建议值 |
|---|---|---|
failureRateThreshold | 触发熔断的失败率 | 50% |
waitDurationInOpenState | 熔断后等待时间 | 30–60s |
permittedNumberOfCallsInHalfOpenState | 半开试探请求数 | 5–10 |
slidingWindowSize | 统计窗口 | 100 |
minimumNumberOfCalls | 计算失败率前的最少调用数 | 20(默认等于窗口大小) |
slowCallRateThreshold | 慢调用熔断阈值 | 80% |
slowCallDurationThreshold | 慢调用定义 | 2s |
下一节 11.3 限流 :令牌桶、漏桶、Redis 分布式限流与 Resilience4j RateLimiter。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。