熔断与降级模式:熔断器模式原理、降级策略、熔断与降级的区别、Hystrix/Resilience4j实践

这一章我们来聊聊熔断与降级。这两个概念,很多同学容易搞混。我刚开始带团队的时候,也踩过这个坑——以为熔断就是降级,降级就是熔断。其实它们俩是两回事,但配合起来用,效果非常好。

熔断器模式原理

熔断器这个名字,是从电路里借来的。你想想看,家里电路短路了,保险丝会烧断,保护整个电路不被烧毁。微服务里的熔断器,干的是同样的事。

当一个服务调用另一个服务时,如果对方一直超时或报错,调用方不能无限等下去。否则,调用方自己的线程池会被占满,最终把自己也拖垮。这就是所谓的「雪崩效应」。

熔断器有三个状态:

  • 关闭(Closed):正常状态,请求正常通过。但会统计失败次数。
  • 打开(Open):失败率达到阈值,直接拒绝请求,快速返回失败。不再发起真实调用。
  • 半开(Half-Open):过了一段时间,放少量请求过去试试。如果成功了,就关闭熔断器;如果还失败,继续保持打开。

核心要点:熔断器不是永久的。它会自动尝试恢复,这是它和手动降级最大的区别。

我在项目中遇到过这样一个场景:某个老系统依赖的第三方支付接口,每到月底就会卡顿。如果没有熔断器,整个订单服务都会被拖死。加了熔断之后,支付接口挂了,订单服务还能正常创建订单,只是支付状态暂时标记为「待处理」。

降级策略

降级,说白了就是「保底方案」。当服务不可用或熔断触发时,我们给用户一个备选响应,而不是直接抛异常。

常见的降级策略有这几种:

策略 说明 适用场景
返回默认值 直接返回一个预设的默认结果 查询类接口,比如商品详情降级为缓存数据
返回兜底数据 从本地缓存或静态文件中读取数据 配置中心挂了,用本地配置文件
异步处理 把请求丢到消息队列里,等恢复后再处理 非实时性要求高的操作,比如发邮件
功能裁剪 关闭非核心功能,保证核心功能可用 大促期间,关闭评论、推荐等次要服务

我的习惯:降级策略一定要提前设计好,别等线上出问题了再临时想。我曾经吃过这个亏——半夜被叫起来,临时写降级代码,手忙脚乱还写了个bug。

熔断与降级的区别

这个问题面试经常问。我简单总结一下:

  • 熔断是手段,降级是结果。熔断器打开了,触发降级逻辑。
  • 熔断是自动的,基于指标(失败率、超时时间)自动触发。降级可以是自动的,也可以是手动的。
  • 熔断保护的是调用方,防止自己被拖垮。降级保护的是用户体验,给用户一个「还能用」的感觉。

举个例子你就明白了:

你打电话给客服,打不通(熔断触发)。然后你听到语音提示「当前坐席繁忙,请稍后再拨」(降级响应)。熔断决定了「打不通」这个行为,降级决定了「给你什么反馈」。

注意:不要把所有降级都绑定在熔断上。有时候服务没熔断,但我们需要主动降级——比如大促时手动关闭非核心功能。这叫「主动降级」,和熔断无关。

Hystrix 实践

Hystrix 是 Netflix 开源的熔断降级框架。虽然现在官方已经进入维护模式,但很多老项目还在用。我简单说一下它的用法。

引入依赖:

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.18</version>
</dependency>

写一个命令:

public class GetUserCommand extends HystrixCommand<String> {

    private final Long userId;

    public GetUserCommand(Long userId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserService"))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withCircuitBreakerRequestVolumeThreshold(10)   // 10秒内请求数
                                .withCircuitBreakerErrorThresholdPercentage(50) // 错误率50%触发熔断
                                .withCircuitBreakerSleepWindowInMilliseconds(5000) // 5秒后尝试半开
                ));
        this.userId = userId;
    }

    @Override
    protected String run() {
        // 真实的远程调用
        return userService.getUserById(userId);
    }

    @Override
    protected String getFallback() {
        // 降级逻辑
        return "{\"id\": " + userId + ", \"name\": \"未知用户\"}";
    }
}

调用方式:

String result = new GetUserCommand(1001L).execute();

避坑指南:Hystrix 的线程池隔离模式,每个命令组都有自己的线程池。我曾经遇到过一个坑——线程池配得太小,正常流量下没问题,一有突发流量就触发熔断。后来我把线程池大小和队列长度调大了一些,才稳定下来。

Resilience4j 实践

Resilience4j 是 Hystrix 的替代品,轻量级,设计更优雅。它不依赖 Netflix 的其它组件,适合 Spring Boot 2.x 和 Spring Cloud 的新项目。

引入依赖:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.7.1</version>
</dependency>

配置熔断器:

resilience4j.circuitbreaker:
  instances:
    userService:
      registerHealthIndicator: true
      slidingWindowSize: 10
      minimumNumberOfCalls: 5
      failureRateThreshold: 50
      waitDurationInOpenState: 5s
      permittedNumberOfCallsInHalfOpenState: 3
      automaticTransitionFromOpenToHalfOpenEnabled: true

代码中使用:

@Service
public class UserService {

    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    public String getUser(Long userId) {
        // 调用远程服务
        return restTemplate.getForObject("http://user-service/users/" + userId, String.class);
    }

    public String getUserFallback(Long userId, Throwable t) {
        // 降级逻辑
        return "{\"id\": " + userId + ", \"name\": \"降级用户\"}";
    }
}

Resilience4j 还支持重试、限流、舱壁隔离等模式。我个人比较喜欢它的链式调用风格:

// 组合使用熔断、重试、限流
Retry retry = Retry.ofDefaults("backendService");
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendService");
RateLimiter rateLimiter = RateLimiter.ofDefaults("backendService");

Supplier<String> decoratedSupplier = Decorators.ofSupplier(() -> backendService.doSomething())
        .withCircuitBreaker(circuitBreaker)
        .withRetry(retry)
        .withRateLimiter(rateLimiter)
        .decorate();

String result = Try.ofSupplier(decoratedSupplier).recover(throwable -> "fallback").get();

总结一下:熔断和降级是系统稳定性的两道防线。熔断负责「挡」,降级负责「兜」。Hystrix 虽然老了,但思想值得学习。新项目我建议直接用 Resilience4j,更轻、更灵活。

嗯,这一章就到这里。下一章我们聊聊「重试与退避模式」,到时候我会分享一个我踩过的重试风暴的坑,保证让你印象深刻。