4. 熔断器模式:熔断器三态、熔断阈值设置、熔断恢复策略、Hystrix与Resilience4j实践

熔断器模式,说白了就是给系统装一个“保险丝”。

我在2016年做一个电商大促项目时,遇到过这么个事:订单服务挂了,结果所有依赖它的服务都在疯狂重试,最后整条链路都瘫痪了。嗯,那次事故之后,我就在团队里全面推了熔断器模式。

4.1 熔断器三态:关闭、打开、半开

熔断器不是只有“断”和“通”两种状态。它有三个状态,我习惯叫它“三态模型”。

  • 关闭状态(Closed):正常情况。请求直接通过,但会统计失败率。
  • 打开状态(Open):失败率超标了。请求直接返回错误,不调用下游。
  • 半开状态(Half-Open):过了一段时间,放少量请求过去试试。成功了就关闭,失败了继续打开。

核心逻辑:关闭时计数,打开时快速失败,半开时试探恢复。

你想想看,如果没有半开状态,熔断器一旦打开,就永远卡死了。半开状态就是那个“试探性恢复”的机制。

熔断器三态流转图 关闭 (Closed) 请求正常通过 统计失败率 打开 (Open) 请求直接拒绝 快速失败 半开 (Half-Open) 试探性放行 决定恢复或继续熔断 失败率超标 超时后自动进入 试探成功 → 关闭 试探失败 → 重新打开 关闭 → 打开:失败率达到阈值 打开 → 半开:等待超时时间(如5秒) 半开 → 关闭/打开:根据试探请求结果决定

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