熔断详解

熔断(Circuit Breaker) 是高可用系统应对「下游故障」的核心手段:当被调用的服务出现持续错误或超时时,熔断器主动「跳闸」,在一段时间内直接快速失败、不再发起真实调用,从而避免上游线程被拖死、引发级联雪崩。

限流是「我主动控制流量」,熔断是「下游病了我不再打扰它」——建议和「限流详解」对照着看,两者正好凑成稳定性三板斧的两条腿。下面就从故障怎么传染说起,一路讲透三态状态机、触发条件、恢复策略、主流框架,以及和限流/降级/重试怎么打配合。

本文脉络:
一 为什么需要熔断(级联雪崩与故障传播)
二 熔断器状态机(Closed / Open / Half-Open)
三 触发条件(错误率 / 超时 / 失败次数)
四 恢复策略(探测窗口、半开试探)
五 常见熔断框架(Hystrix / Resilience4j / Sentinel)
六 熔断后的兜底(降级 / Fallback)
七 与限流、降级、重试的协同(稳定性三板斧)
八 生产实践与常见陷阱
九 面试速答

一、为什么需要熔断

1.1 故障是如何传播的

分布式系统中,一次请求往往要经过多个下游服务。任何一个下游变慢或故障,都会沿调用链向上传染

调用链: 用户中心 → 订单服务 → 支付服务 → 银行网关

银行网关抖动(响应从 50ms 变成 5s):

支付服务线程被占满(都在等银行网关响应)

支付服务线程池耗尽,自身请求也开始排队

订单服务调用支付服务超时,订单服务线程也被占满

用户中心调用订单服务超时……

整个调用链雪崩,全站不可用

核心问题:下游一旦变慢(而不是立刻报错),上游会「执着地等待」,把宝贵的线程/连接资源耗尽在自己这一侧——这比下游直接挂掉更危险。

1.2 单纯的超时和重试为什么不够

手段 作用 局限
超时 避免无限等待 超时时间内仍占用线程;故障持续时每次都等到超时才释放
重试 应对瞬时抖动 下游持续故障时,重试反而放大流量,加速雪崩
线程池隔离 限制单一下游占用的资源 池子打满后,该下游的正常请求也受影响

超时和重试是「每次调用独立判断」,没有记忆。当下游已经持续故障时,继续调用它毫无意义——明明知道会失败,却每次都要等一个超时才放弃。

熔断的本质:给调用加上「记忆」——下游连续失败到一定程度,就认定它「病了」,在一段时间内直接快速失败,不再发起真实调用,给下游恢复的时间,也保护自己不被拖死。


二、熔断器状态机(Closed / Open / Half-Open)

这是熔断器的核心,类比电气保险丝:电流过大时保险丝熔断(Open),保护电路;修复后人工复位(Closed)。

2.1 三态模型

                 失败率达阈值
┌────────────┐ ─────────────────► ┌────────────┐
│ Closed │ │ Open │
│ (关闭) │ ◄───────────────── │ (打开) │
│ 正常放行 │ 半开探测成功 │ 快速失败 │
└────────────┘ └─────┬──────┘
▲ │
│ │ 等待超时时间
│ 半开探测全部成功 │ (sleep window)
│ ▼
│ ┌────────────┐
└───────────────────────── │ Half-Open │
│ (半开) │
│ 放行少量试探 │
└─────┬──────┘

试探失败 ─┴─ 试探成功
↓ ↓
回到 Open 回到 Closed

2.2 三个状态详解

状态 行为 转入条件
Closed(关闭) 熔断器不工作,所有请求正常放行,统计失败率 初始状态 / Half-Open 探测成功
Open(打开) 所有请求直接快速失败(走降级/Fallback),不发起真实调用 Closed 状态下失败率超阈值
Half-Open(半开) 放行少量请求做试探,其余快速失败 Open 状态持续一段时间(sleep window)后自动转入

2.3 Open 状态:全拒,不是按比例拒

限流和熔断最容易混的地方就在这——限流是「按比例放行」,熔断 Open 是「全部拒绝」。先给结论:Open 状态下,100% 的请求都直接快速失败,没有「按比例拒绝」这一说。

Open 期间(如 30s 冷却):
请求1 → 快速失败(走降级)
请求2 → 快速失败(走降级)
...
请求N → 快速失败(走降级)

→ 没有一个请求会真正打到下游,是「一刀切」

为什么必须是全拒:Open 的目的就是「认定下游病了,这段时间完全不打扰它」,给下游完整的恢复时间。如果还放一部分请求过去,下游就始终被持续打扰、没机会恢复,熔断的意义就没了。

「按比例放行」不是 Open 的事,而是 Half-Open 的事——而且即便在 Half-Open,主流框架通常也不是「按百分比」,而是一个固定的试探数量

框架 Half-Open 放行策略
Hystrix 默认只放行 1 个请求试探
Resilience4j 放行 10 个permittedNumberOfCallsInHalfOpenState,可配)
Sentinel 熔断时间窗结束后进入半开探测
部分高级实现 渐进式恢复:按 10% → 30% → 60% → 100% 逐步放行

换句话说:真正的「百分比拒绝」是限流的特征,不是熔断 Open 的特征。限流和熔断的完整对比放在第七章,这里先记住这点就行。

2.4 一个完整周期

1. Closed 状态:正常处理 1000 个请求,统计窗口内错误率
↓ 错误率从 1% 升到 55%(超过阈值 50%)
2. Open 状态:直接快速失败,给下游 30s 恢复时间
↓ 30s 计时结束
3. Half-Open 状态:放行 5 个探测请求

┌───────────────────────────────┐
│ 5 个全部成功 → 回到 Closed │(下游恢复了)
│ 有失败 → 回到 Open │(再等一个周期)
└───────────────────────────────┘

为什么需要 Half-Open:直接从 Open 切回 Closed 风险太大——一旦流量瞬间涌入,下游可能还没真正恢复又被压垮。Half-Open 用「少量试探」谨慎验证,是「灰度恢复」思想在熔断里的体现。


三、触发条件

熔断打开的依据是「下游健康度」,主流框架的判定维度:

维度 含义 典型阈值
错误率 统计窗口内失败请求占比 错误率 ≥ 50%
慢调用率 统计窗口内超时请求占比 慢调用率 ≥ 60%
失败次数 统计窗口内绝对失败数 1 分钟失败 ≥ 100 次

3.1 错误率(最常用)

统计窗口:最近 10 秒
最小请求数:20(样本太少不统计,避免误判)
错误率阈值:50%

时间线(每秒采样):
t1: 请求 30 个,失败 5 → 错误率 17% → 放行
t2: 请求 50 个,失败 28 → 错误率 56% → 超阈值,Open!

为什么要有「最小请求数」:如果窗口里只有 2 个请求且都失败了,错误率 100% 看起来很严重,但其实只是样本不足。设最小请求数(如 20)保证统计才有意义。

3.2 慢调用率(应对「假死」)

有些故障不是直接报错,而是响应变慢。错误率熔断捕捉不到「全部超时但都返回 200」的假死场景,于是引入慢调用率:

慢调用定义:RT > maxRT(如 500ms)
慢调用率阈值:60%

统计窗口内:50 个请求中 35 个 RT > 500ms
→ 慢调用率 70% > 60% → Open

适用:对延迟敏感、且容易「慢但不错」的下游(如依赖大模型、搜索引擎的接口)。


四、恢复策略

熔断打开后,何时如何回到正常状态,决定了系统的恢复体验。

4.1 sleep window(冷却时间)

Open 持续的时间,给下游喘息恢复。典型 5s~60s。

太短:下游还没恢复就试探,再次失败 → 反复 Open/Half-Open 抖动
太长:下游已恢复,但用户长时间感受故障
实践:初始 10~30s,必要时配合指数退避(连续失败则延长冷却)

4.2 Half-Open 探测策略

策略一:固定请求数试探
放行 N 个请求(如 5 个),全成功 → Closed;有失败 → Open

策略二:限时试探
在 T 秒内(如 10s)按比例放行,统计成功率

策略三:渐进放行(自动恢复)
半开后逐步提高放行比例:10% → 30% → 60% → 100%
任何一步失败则回退,更平滑

选型:简单场景用固定请求数(Hystrix 默认);对恢复平滑性要求高用渐进放行(部分新版框架支持)。


五、常见熔断框架

5.1 Hystrix(Netflix,经典但已停止维护)

// 继承 HystrixCommand,声明熔断 + 降级
public class QueryOrderCommand extends HystrixCommand<Order> {
private final String orderId;

public QueryOrderCommand(String orderId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("OrderService"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(20) // 最小请求数
.withCircuitBreakerErrorThresholdPercentage(50) // 错误率阈值 50%
.withCircuitBreakerSleepWindowInMilliseconds(10000) // 冷却 10s
));
this.orderId = orderId;
}

@Override
protected Order run() {
return orderService.query(orderId); // 真实调用
}

@Override
protected Order getFallback() {
return Order.defaultOrder(); // 熔断/异常时的降级
}
}

特点:经典实现,基于线程池/信号量隔离 + 熔断 + 降级。2018 年起进入维护模式,不建议新项目使用,但其设计思想是后续框架的基础。

5.2 Resilience4j(Hystrix 的现代继任者)

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 错误率阈值 50%
.slowCallRateThreshold(60) // 慢调用率阈值 60%
.slowCallDurationThreshold(Duration.ofMillis(500)) // 慢调用定义 500ms
.minimumNumberOfCalls(20) // 最小请求数
.waitDurationInOpenState(Duration.ofSeconds(10)) // 冷却 10s
.slidingWindowSize(10) // 滑动窗口
.build();

CircuitBreaker cb = CircuitBreaker.of("orderService", config);

// 函数式包装
Order order = cb.executeSupplier(() -> orderService.query(orderId));

特点

  • 基于 Java 8 函数式编程,轻量无依赖
  • 支持错误率 + 慢调用率双重判定
  • 与 Spring Boot / Reactor 深度集成
  • 新项目主流选择

5.3 Sentinel(阿里,国内主流)

// 通过资源名 + 规则配置熔断
DegradeRule rule = new DegradeRule("queryOrder")
.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO) // 慢调用比例
.setCount(500) // 慢调用阈值 RT:500ms
.setSlowRatioThreshold(0.6) // 慢调用比例阈值 60%
.setMinRequestAmount(20) // 最小请求数
.setStatIntervalMs(10000) // 统计窗口 10s
.setTimeWindow(10); // 熔断时长 10s

DegradeRuleManager.loadRules(Collections.singletonList(rule));

try (Entry entry = SphU.entry("queryOrder")) {
orderService.query(orderId);
} catch (BlockException e) {
fallback(); // 被熔断/限流
}

特点

  • 熔断 + 限流 + 系统自适应保护一体
  • 支持错误率、慢调用率、异常数三种策略
  • 带可视化 Dashboard,可动态调整规则、实时查看熔断状态
  • 国内 Java 生态最主流

六、熔断后的兜底(降级 / Fallback)

熔断打开后请求会快速失败,但对用户的最终表现取决于降级逻辑。没有降级的熔断等于「直接报错」,体验很差。

6.1 常见降级手段

场景 降级方式
读接口(商品详情) 返回缓存/本地默认值/热门推荐
列表查询 返回首屏固定数据,隐藏非核心模块
写接口(下单) 不能降级为假成功!改为异步化(写 MQ 后提示「处理中」)
非核心功能(推荐、评论) 直接隐藏模块,保证主流程
@Override
protected Order getFallback() {
// 读降级:返回缓存,避免空白页
Order cached = cache.get(orderId);
return cached != null ? cached : Order.defaultOrder();
}

6.2 降级的关键原则

① 降级要快:不能再调用会失败的服务(否则降级没意义)
② 降级要「有用」:返回用户能接受的数据,而不是 null/报错
③ 写操作慎降级:不能把「失败」伪装成「成功」,应转为异步处理
④ 降级也要可监控:降级触发量激增本身是重要告警

七、与限流、降级、重试的协同(稳定性三板斧)

这一节与「限流详解」第八章呼应,建议对照阅读。

7.1 四者的职责分工

┌──────────────────────────────────────────────────────┐
│ 限流(Rate Limiting) │
│ 主动控制「进来多少」——保护自己/下游不被流量打垮 │
│ 触发条件:流量超阈值 │
├──────────────────────────────────────────────────────┤
│ 熔断(Circuit Breaker) ← 本文主角 │
│ 被动响应「下游挂没挂」——下游故障时快速失败,不再调用 │
│ 触发条件:下游错误率/超时率超阈值 │
├──────────────────────────────────────────────────────┤
│ 降级(Degradation) │
│ 「挂了/限流了之后给什么」——返回兜底数据 │
│ 触发条件:限流、熔断、异常时 │
├──────────────────────────────────────────────────────┤
│ 重试(Retry) │
│ 「失败了再试一次」——应对瞬时故障 │
│ 触发条件:超时、网络抖动 │
└──────────────────────────────────────────────────────┘

7.2 熔断与重试的冲突(重点)

危险组合:上游重试 + 下游故障

上游调用下游失败 → 重试(流量 ×2)
→ 下游更扛不住 → 错误率更高 → 触发熔断(正确!熔断在这里救场)

如果只靠重试没有熔断:
上游不断重试 → 流量持续放大 → 把下游彻底压垮,自己也跟着死

结论:重试必须配合熔断。
熔断打开期间,重试应当被抑制(直接走降级)。
重试本身也要有退避(backoff)和次数上限。

7.3 限流 vs 熔断(最易混淆)

维度 限流 熔断
主动/被动 主动(我决定放多少进来) 被动(下游状态决定调不调)
保护对象 保护自己/下游的容量 保护下游不再被请求压垮
触发依据 流量/QPS 错误率/超时率/失败次数
触发后 部分请求拒绝 全部请求快速失败(一段时间)
状态 无状态(每个请求独立判断) 有状态(开/半开/关)

一句话区分:限流是「我主动控制流量」,熔断是「下游病了我不再打扰它」。


八、生产实践与常见陷阱

8.1 最佳实践

① 熔断要配在「依赖外部/不可控」的调用上:
- 调第三方 API、网关、下游服务
- 不需要给纯本地内存操作加熔断

② 设最小请求数:样本不足时不要熔断,避免误判(如刚启动时)

③ 熔断 + 降级 + 告警 三件套:
熔断触发 → 自动降级保体验 → 同时告警让人介入

④ 关键参数要可动态调整:
生产环境的阈值/冷却时间,应支持不重启就改(Sentinel Dashboard)

⑤ 区分核心与非核心依赖:
非核心依赖熔断后直接降级;核心依赖熔断要考虑有损而非全损

8.2 常见陷阱

陷阱 后果 对策
阈值过低 正常抖动就熔断,频繁误断 设最小请求数 + 合理阈值,参考历史 P99
冷却时间太短 反复 Open/Half-Open 抖动 冷却时间 > 下游平均恢复时间
降级调用同一故障源 降级也失败,等于没降级 降级走缓存/本地数据,不再调故障下游
熔断粒度过粗 一个下游故障影响所有调用方 按接口/资源粒度分别熔断(资源隔离)
只熔断不告警 故障被「掩盖」,无人知晓 熔断状态变化必须触发告警

九、面试速答

问题 速答要点
熔断是什么?解决什么问题? 下游持续故障时主动「跳闸」快速失败,避免上游线程被拖死、级联雪崩。解决「故障传播」。
熔断器有几个状态? 三态:Closed(正常放行)/ Open(全部快速失败)/ Half-Open(少量试探恢复)。
为什么需要 Half-Open? 直接切回 Closed 风险大,Half-Open 用少量请求试探,是「灰度恢复」,避免流量瞬间涌入再次压垮下游。
熔断的触发条件? 错误率、慢调用率、失败次数。最常用错误率,慢调用率用于捕捉「慢但不错」的假死。
熔断和限流的区别? 限流主动控制进来的流量(看 QPS);熔断被动响应下游故障(看错误率),熔断后全部快速失败。
熔断和重试的关系? 熔断打开时应抑制重试。只有重试没有熔断,故障时重试会放大流量、加速雪崩。
常见的熔断框架? Hystrix(经典、已停维护)、Resilience4j(Hystrix 继任者、新项目主流)、Sentinel(阿里、国内主流、带 Dashboard)。
熔断打开后怎么办? 走降级(Fallback):读接口返回缓存/默认值,写接口转异步,对用户返回可接受的数据而非报错。