Resilience4j 服务容错与熔断
导学
2020 年双十一凌晨 2 点,飞翔科技系统告警:支付服务响应变慢,线程池堆积,最终导致整个商城 502。
CTO 大翔事后复盘:"为什么支付服务挂了,连商品浏览都不可用?"
白歌分析道:"订单服务调用支付服务超时后,RestTemplate 会一直等待直到自身线程池耗尽——这就是服务雪崩。我们需要熔断器。"
服务雪崩原理:
定位与问题场景
Resilience4j 是 Netflix Hystrix 的替代品,为 Spring Cloud 提供熔断、限流、重试、隔离四大容错能力:
| 能力 | 解决的问题 |
|---|---|
| Circuit Breaker(熔断) | 下游服务异常时快速失败,防止级联故障 |
| Rate Limiter(限流) | 限制调用频率,防止流量突增压垮下游 |
| Retry(重试) | 临时故障时自动重试 |
| Bulkhead(隔离) | 限制并发数,防止单个服务耗尽所有线程 |
| Time Limiter(超时) | 设置调用超时上限 |
熔断器核心原理
三态状态机
三态说明:
| 状态 | 行为 | 触发条件 |
|---|---|---|
| CLOSED | 正常调用,统计失败率 | 启动默认状态 |
| OPEN | 拒绝所有请求,直接抛 CallNotPermittedException | 滑动窗口内失败率 ≥ failureRateThreshold |
| HALF_OPEN | 放行部分请求探测服务是否恢复 | OPEN 状态持续 waitDurationInOpenState 后 |
滑动窗口统计
Resilience4j 使用滑动窗口统计最近 N 次调用的成功/失败:
完整示例:飞翔科技订单服务熔断降级
场景描述
订单服务调用支付服务,当支付服务超时或异常时触发熔断,返回降级响应。
操作前后对比:
| 维度 | 引入 Resilience4j 前 | 引入 Resilience4j 后 |
|---|---|---|
| 支付服务超时 | 订单线程阻塞,级联故障 | 熔断器快速失败,返回降级响应 |
| 支付服务恢复 | 人工重启后才能恢复 | 半开状态自动探测恢复 |
| 可观测性 | 无 | 熔断状态变更事件 + 指标 |
步骤一:依赖引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
步骤二:基础熔断配置
resilience4j:
circuitbreaker:
configs:
default:
sliding-window-type: COUNT_BASED # 基于调用次数统计
sliding-window-size: 10 # 窗口大小 10 次
failure-rate-threshold: 50 # 失败率阈值 50%
wait-duration-in-open-state: 10s # OPEN 状态等待 10s 后进入 HALF_OPEN
permitted-number-of-calls-in-half-open-state: 3 # HALF_OPEN 允许 3 次探测
automatic-transition-from-open-to-half-open-enabled: true
instances:
paymentService:
base-config: default
minimum-number-of-calls: 5 # 至少 5 次调用后才计算失败率
步骤三:代码实现
@RestController
public class OrderController {
@Autowired
private PaymentClient paymentClient;
@GetMapping("/order/pay/{orderId}")
// 引入 Resilience4j 前:无保护,支付服务挂了就一起挂
// public Map<String, Object> pay(@PathVariable String orderId) {
// return paymentClient.pay(orderId);
// }
// 引入 Resilience4j 后:熔断保护
@CircuitBreaker(
name = "paymentService",
fallbackMethod = "payFallback"
)
public Map<String, Object> pay(@PathVariable String orderId) {
return paymentClient.pay(orderId);
}
// 降级方法:必须与原方法相同的参数 + Throwable
public Map<String, Object> payFallback(String orderId, Throwable e) {
log.warn("支付服务熔断降级,orderId={},原因:{}", orderId, e.getMessage());
return Map.of(
"orderId", orderId,
"status", "PENDING",
"message", "支付服务繁忙,订单已暂存,稍后自动重试"
);
}
}
步骤四:声明式集成(Feign + CircuitBreaker)
@FeignClient(
name = "FEIXIANG-PAYMENT-SERVICE",
fallbackFactory = PaymentClientFallbackFactory.class
)
public interface PaymentClient {
@PostMapping("/payments")
Map<String, Object> pay(@RequestBody PaymentRequest request);
}
@Component
@Slf4j
public class PaymentClientFallbackFactory implements FallbackFactory<PaymentClient> {
@Override
public PaymentClient create(Throwable cause) {
if (cause instanceof CallNotPermittedException) {
log.error("支付服务熔断器 OPEN!");
}
return request -> Map.of(
"status", "FALLBACK",
"message", "支付服务暂时不可用:" + cause.getMessage()
);
}
}
步骤五:熔断器事件监听
@Component
public class CircuitBreakerEventListener {
@EventListener
public void onStateTransition(CircuitBreakerOnStateTransitionEvent event) {
log.warn("熔断器 [{}] 状态变更:{} → {}",
event.getCircuitBreakerName(),
event.getStateTransition().getFromState(),
event.getStateTransition().getToState()
);
}
}
其他 Resilience4j 模块
Retry(重试)
resilience4j:
retry:
instances:
paymentRetry:
max-attempts: 3
wait-duration: 500ms
retry-exceptions:
- java.net.SocketTimeoutException
@Retry(name = "paymentRetry", fallbackMethod = "payFallback")
public Map<String, Object> pay(String orderId) {
return paymentClient.pay(orderId);
}
Bulkhead(舱壁隔离)
resilience4j:
bulkhead:
instances:
paymentBulkhead:
max-concurrent-calls: 10 # 最多 10 个并发
max-wait-duration: 500ms # 等待队列超时
信号量隔离 vs 线程池隔离:Resilience4j 默认使用信号量隔离,不创建额外线程。
TimeLimiter(超时控制)
resilience4j:
timelimiter:
instances:
paymentTimeLimiter:
timeout-duration: 3s
易错场景
1. fallback 方法签名不匹配
// ❌ 错误:缺少 Throwable 参数
public Map<String, Object> payFallback(String orderId) { ... }
// ✅ 正确:参数类型、顺序、数量完全一致 + Throwable
public Map<String, Object> payFallback(String orderId, Throwable e) { ... }
2. 熔断器配置名称与 @CircuitBreaker name 不一致
// @CircuitBreaker(name = "paymentService") ← 此处
// resilience4j.circuitbreaker.instances.paymentService: ← 必须与此处一致
3. 同一方法多个 Resilience4j 注解顺序问题
// Resilience4j 按注解声明顺序执行
// 推荐顺序:Bulkhead → TimeLimiter → CircuitBreaker → Retry
@Bulkhead(name = "...")
@CircuitBreaker(name = "...")
@Retry(name = "...")
public Map<String, Object> pay(String orderId) { ... }
4. 将业务异常统计为熔断失败
默认所有异常(包括 IllegalArgumentException)都被计入失败统计。应配置 ignoreExceptions:
resilience4j:
circuitbreaker:
instances:
paymentService:
ignore-exceptions:
- com.feixiang.exception.BusinessException
面试考点
Resilience4j 三种状态及转换条件?
CLOSED(正常放行,统计失败率)→ 失败率超过阈值 → OPEN(快速失败,拒绝请求)→ 等待时间过后 → HALF_OPEN(放行少量请求探测)→ 探测成功回到 CLOSED,探测失败回到 OPEN。
服务雪崩如何防止?
三层防护:
- 熔断(Circuit Breaker):下游故障时快速失败,防止线程阻塞
- 舱壁(Bulkhead):隔离不同服务的线程资源,防止一个服务耗尽所有线程
- 降级(Fallback):返回兜底响应,保证用户体验不中断
Resilience4j 和 Hystrix 的区别?
Hystrix Resilience4j 已停止维护 活跃维护 强制线程池隔离 信号量隔离(轻量) 架构较重 函数式,轻量,无外部依赖 通过 Archaius 配置 原生 Spring 配置 仅支持熔断+隔离 熔断+限流+重试+隔离+超时
小结
Resilience4j 通过熔断器三态状态机阻止了故障的级联传播,配合重试、限流、舱壁隔离,构成了微服务的多层容错体系。下一节我们看来自阿里巴巴的 Sentinel,它在限流和流量整形方面有更强的表现。