4. 熔断器模式:熔断器三态、熔断阈值设置、熔断恢复策略、Hystrix与Resilience4j实践
熔断器模式,说白了就是给系统装一个“保险丝”。
我在2016年做一个电商大促项目时,遇到过这么个事:订单服务挂了,结果所有依赖它的服务都在疯狂重试,最后整条链路都瘫痪了。嗯,那次事故之后,我就在团队里全面推了熔断器模式。
4.1 熔断器三态:关闭、打开、半开
熔断器不是只有“断”和“通”两种状态。它有三个状态,我习惯叫它“三态模型”。
- 关闭状态(Closed):正常情况。请求直接通过,但会统计失败率。
- 打开状态(Open):失败率超标了。请求直接返回错误,不调用下游。
- 半开状态(Half-Open):过了一段时间,放少量请求过去试试。成功了就关闭,失败了继续打开。
核心逻辑:关闭时计数,打开时快速失败,半开时试探恢复。
你想想看,如果没有半开状态,熔断器一旦打开,就永远卡死了。半开状态就是那个“试探性恢复”的机制。
4.2 熔断阈值设置
阈值设多少?这是个好问题。我见过有人设1%,结果网络抖动一下就熔断了。也有人设90%,熔断器基本没起过作用。
我个人习惯看这几个指标:
| 参数 | 说明 | 建议值 |
|---|---|---|
| 失败率阈值 | 失败请求占比超过此值则熔断 | 50% - 70% |
| 请求数阈值 | 达到此请求数才开始计算失败率 | 10 - 20 次 |
| 统计窗口 | 滑动窗口的时间范围 | 10 - 30 秒 |
我的经验:不要只设一个固定阈值。我一般会结合业务特性来调。比如支付接口,失败率阈值设低一点(40%),因为支付失败影响大。日志上报接口,设高一点(80%),偶尔失败无所谓。
4.3 熔断恢复策略
熔断之后怎么恢复?这里有几个策略:
- 基于时间的恢复:熔断打开后,等N秒自动进入半开。N秒怎么定?我一般设5-10秒,太短容易反复熔断,太长影响恢复速度。
- 基于试探次数的恢复:半开状态下,放行M个请求。如果全部成功,就关闭。如果有一个失败,就重新打开。M我通常设1-3个。
- 渐进式恢复:第一次熔断等5秒,第二次等10秒,第三次等20秒。指数退避。这个策略我在高并发场景下用得比较多。
注意:恢复策略不能太激进。我曾经把恢复时间设成1秒,结果服务刚缓过来又被熔断了,来回折腾了十几分钟。后来改成5秒起步,问题就解决了。
4.4 Hystrix 实践
Hystrix 是 Netflix 开源的熔断器库。虽然现在进入维护模式了,但很多老项目还在用。我简单说一下用法。
// HystrixCommand 示例
public class OrderServiceCommand extends HystrixCommand<String> {
private final String orderId;
public OrderServiceCommand(String orderId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("OrderGroup"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(true)
.withCircuitBreakerRequestVolumeThreshold(10) // 10个请求开始统计
.withCircuitBreakerErrorThresholdPercentage(50) // 50%失败率熔断
.withCircuitBreakerSleepWindowInMilliseconds(5000) // 5秒后半开
));
this.orderId = orderId;
}
@Override
protected String run() throws Exception {
// 调用远程订单服务
return restTemplate.getForObject("http://order-service/order/" + orderId, String.class);
}
@Override
protected String getFallback() {
return "{\"status\": \"fallback\", \"orderId\": \"" + orderId + "\"}";
}
}
Hystrix 的配置项很多,但核心就三个:requestVolumeThreshold(请求数阈值)、errorThresholdPercentage(失败率阈值)、sleepWindowInMilliseconds(恢复等待时间)。
注意:Hystrix 的滑动窗口默认是10秒。也就是说,它统计的是最近10秒内的请求。这个窗口大小也可以调,但我建议保持默认。
4.5 Resilience4j 实践
Resilience4j 是 Hystrix 的替代品。它更轻量,而且支持 Java 8 的函数式编程。我现在的新项目都用它。
// Resilience4j 配置
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失败率阈值 50%
.slidingWindowSize(10) // 滑动窗口大小 10
.minimumNumberOfCalls(5) // 最少调用次数 5
.waitDurationInOpenState(Duration.ofSeconds(5)) // 打开状态等待 5 秒
.permittedNumberOfCallsInHalfOpenState(3) // 半开状态允许 3 次试探
.build();
CircuitBreaker circuitBreaker = CircuitBreaker.of("orderService", config);
// 使用
Supplier<String> decoratedSupplier = CircuitBreaker.decorateSupplier(
circuitBreaker,
() -> restTemplate.getForObject("http://order-service/order/123", String.class)
);
// 执行并处理降级
Try<String> result = Try.ofSupplier(decoratedSupplier)
.recover(throwable -> "{\"status\": \"fallback\"}");
Resilience4j 的配置更直观。我个人比较喜欢它的 slidingWindowSize 参数,可以精确控制统计窗口的大小。
小技巧:Resilience4j 支持事件监听。你可以监听熔断器的状态变化,然后打日志或者发告警。我在生产环境就是这么做的,一旦熔断器打开,立马通知值班人员。
// 事件监听示例
circuitBreaker.getEventPublisher()
.onStateTransition(event -> {
log.info("熔断器状态变化: {} -> {}", event.getStateTransition());
// 发送告警
alertService.sendAlert("熔断器状态变化: " + event.getStateTransition());
})
.onFailureRateExceeded(event -> {
log.warn("失败率超标: {}", event.getFailureRate());
});
4.6 避坑指南
最后,分享几个我踩过的坑:
- 不要对所有接口用同一套熔断配置。我曾经图省事,所有服务都用一样的阈值。结果核心接口频繁熔断,非核心接口反而没熔断过。后来我按接口的重要性和调用量分别配置。
- 熔断器不能替代超时。熔断器管的是“失败率”,超时管的是“单个请求的等待时间”。两个都要设。我见过有人只设熔断器不设超时,结果请求卡死在那里,熔断器统计不到失败。
- 降级逻辑要轻量。降级方法里不要做耗时操作。我见过有人在降级方法里查数据库,结果降级本身也超时了。降级应该返回默认值或缓存数据。
- 监控不能少。熔断器打开了你不知道,等于白设。我习惯把熔断器的状态、失败率、调用量都打到监控系统里。
嗯,熔断器模式就讲到这里。说白了,它就是给系统加了一层保护。设好阈值、配好恢复策略、加上监控,你的系统就能在故障面前“优雅地倒下”,而不是“雪崩式地崩溃”。
公众号:蓝海资料掘金营,微信deep3321