熔断器原理详解:三态、错误率与 fallback
基于 Opossum 8.x · 核于 2026-08
速查
- 状态机三态:Closed(闭合,正常放行+统计)、Open(打开,全拒+等 resetTimeout)、Half-Open(半开,放一个试探)。转换:Closed→Open(错误率超阈值)、Open→Half-Open(resetTimeout 到期)、Half-Open→Closed(试探成功)/Half-Open→Open(试探失败)。
- Closed 状态职责:放行所有请求 + 在滑动窗口内统计错误率 + 检查是否该跳闸。一旦错误率 > errorThresholdPercentage(默认 50%)且调用量 ≥ volumeThreshold(默认 0),触发
open事件,切到 Open。 - Open 状态职责:所有请求直接 reject(触发
reject事件)或走 fallback,根本不调下游。这是「快速失败」——毫秒级返回,不占上游资源。持续 resetTimeout(默认 30s)后切到 Half-Open。 - Half-Open 状态职责:放一个请求试探下游恢复情况。成功 → 触发
close事件回 Closed;失败 → 触发open事件回 Open,重新等 resetTimeout。Half-Open 是「自动恢复」的关键——既试探又不让流量洪流冲击刚恢复的下游。 - 错误率公式:
错误率 = 窗口内失败次数 / 窗口内总调用次数。失败包括:业务函数 reject/throw、超时(timeout 触发)、被熔断 reject(级联)。在 rollingCountTimeout(默认 10s)的滑动窗口内、按 rollingCountBuckets(默认 10 桶)分桶统计。 - volumeThreshold 的作用:最小调用量门槛。窗口内调用量 < volumeThreshold 时不跳闸(即使错误率 100%)——避免「调了 1 次失败就误跳闸」。生产建议设非零值(如 5)。
- fallback 触发条件:①熔断器 Open/Half-Open 时请求被 reject;②调用失败(业务异常);③调用超时。fallback 接收与 fire 相同的参数,返回兜底结果。fallback 被触发仍计为失败(计入错误率),直到 Closed 恢复主路径。
- 事件全清单:
fire(发起调用)、success/failure/timeout(调用结果)、reject(被熔断拒绝)、open/close/halfOpen(状态转换)、fallback(降级触发)、semaphoreLocked(并发达容量上限)、healthCheckFailed(自定义健康检查失败)、shutdown(关闭)。 - 容量(capacity)与并发控制:Opossum 可设
maxCapacity限制最大并发调用数,超出的触发semaphoreLocked拒绝——这是熔断器内置的「信号量」限流,防止并发洪流。 - 健康检查(healthCheckFunc):可配置自定义健康检查函数,Half-Open 时除了试真实调用,也可先跑健康检查判断下游是否活着。
一、Closed 状态:正常放行与统计
Closed 是熔断器的「默认/恢复」状态。在这个状态下:
javascript
// Closed 状态的每次 fire:
breaker.fire(args)
→ 检查是否超容量(capacity) ← 超了触发 semaphoreLocked
→ 检查状态 ← Closed,放行
→ 执行业务函数(带 timeout)
→ 成功:记 success,滑入窗口
→ 失败/超时:记 failure/timeout,滑入窗口
→ 检查窗口:错误率 > 阈值 且 调用量 ≥ volumeThreshold?
→ 是:触发 open 事件,切到 Open
→ 否:继续 Closed- 滑动窗口统计:每次调用结果(成功/失败/超时)都记入当前时间桶。rollingCountTimeout(默认 10s)决定窗口长度,rollingCountBuckets(默认 10)决定桶数(每秒一桶)。窗口随时间滑动,老桶滑出不算。
- volumeThreshold 的意义:避免低调用量下的误判。如果窗口内只调了 1 次且失败了,错误率 100%,但样本太小——设 volumeThreshold=5 表示「至少调 5 次才统计」,5 次以下即使全失败也不跳闸。
- 超时也算失败:单次调用超过 timeout(默认 10s,生产建议调小到秒级)触发
timeout事件,同时计为失败入窗口。
二、Open 状态:快速失败与资源释放
一旦跳到 Open,熔断器的行为彻底改变——根本不调下游:
javascript
// Open 状态的每次 fire:
breaker.fire(args)
→ 检查状态 ← Open
→ 不执行业务函数!直接:
→ 有 fallback:执行 fallback,触发 fallback 事件
→ 无 fallback:reject Promise,触发 reject 事件
→ 毫秒级返回,不占上游线程/连接- 为什么这是核心保护:Open 状态下,原本要等 30s 超时的请求现在毫秒级失败——上游的线程池、连接池瞬间释放,上游自己活下来,能继续服务其他请求。这就是「阻断雪崩」。
- resetTimeout 计时:进入 Open 时开始计时,resetTimeout(默认 30s)到期后切到 Half-Open 试探。这个周期要权衡:太短(频繁试探冲击刚恢复的下游)、太长(下游恢复了也不让用)。
- fallback 仍计失败:即使走了 fallback 返回了兜底数据,熔断器仍把这次记为失败(因为主路径没成功)——错误率继续累积,直到 Half-Open 试探成功切回 Closed。
三、Half-Open 状态:试探恢复
resetTimeout 到期后,熔断器进入 Half-Open——只放一个请求试探:
Open(已等满 resetTimeout)
→ 切到 Half-Open
→ 下一个 fire 请求:实际调用下游(试探)
→ 成功:触发 close 事件 → 回 Closed(恢复正常)
→ 失败:触发 open 事件 → 回 Open(再等一个 resetTimeout)
→ 试探期间的其它请求:仍 reject/fallback- 为什么只放一个:下游可能刚恢复但还很脆弱,如果 Half-Open 立刻放全部流量,可能又把它打挂。放一个请求是「最小试探」——既验证恢复,又不冲击。
- 自动恢复:整个过程无需人工干预——下游修好了,Half-Open 试探成功,自动回 Closed;没修好,继续 Open 等下一轮。运维只需关注监控告警。
- healthCheckFunc:可配置自定义健康检查(如调下游的
/health端点),Half-Open 时先跑健康检查,失败则直接回 Open 不试真实调用,减少对下游的冲击。
四、配置项全景与调参建议
| 配置项 | 默认值 | 含义 | 调参建议 |
|---|---|---|---|
timeout | 10000 (10s) | 单次调用超时(ms),设 false 禁用 | 生产调小到秒级(1-5s),别用默认 10s |
errorThresholdPercentage | 50 | 错误率跳闸阈值(%) | 严格场景 30、宽松场景 60,看业务容忍度 |
resetTimeout | 30000 (30s) | Open 后多久试探(ms) | 下游恢复快的 10s、慢的 60s |
rollingCountTimeout | 10000 (10s) | 统计窗口长度(ms) | 短窗口响应快、长窗口平滑 |
rollingCountBuckets | 10 | 窗口分桶数 | = rollingCountTimeout/1000 通常合适 |
volumeThreshold | 0 | 最小调用量才统计 | 生产必设非零(如 5),防低调用量误判 |
maxCapacity | 无限 | 最大并发调用数 | 防并发洪流,配合信号量限流 |
enabled | true | 是否启用熔断 | 故障排查时可临时 false 关闭 |
cache | false | 是否缓存成功结果 | 适合读多写少的幂等调用 |
调参误区:
- timeout 用默认 10s:太长,下游慢时上游资源被占满,失去保护意义。生产建议 1-5s。
- volumeThreshold 留默认 0:调 1 次失败就跳闸,正常抖动误熔断。生产必设非零。
- errorThresholdPercentage 设太低(如 10):正常抖动就跳闸,频繁误熔断。建议 30-50 起步。
五、事件清单与监控集成
Opossum 熔断器是 EventEmitter,所有关键行为都发事件:
javascript
const breaker = new CircuitBreaker(fn, options);
// 调用生命周期
breaker.on('fire', (args) => log('发起调用', args));
breaker.on('success', (result) => log('成功', result));
breaker.on('failure', (err) => log('失败', err));
breaker.on('timeout', () => log('超时'));
// 熔断器状态
breaker.on('open', () => alert('熔断器打开!')); // Closed→Open
breaker.on('close', () => log('熔断器恢复')); // Half-Open→Closed
breaker.on('halfOpen', () => log('开始试探恢复')); // Open→Half-Open
breaker.on('reject', () => log('请求被熔断拒绝'));
// 降级与限流
breaker.on('fallback', (result) => log('触发降级', result));
breaker.on('semaphoreLocked', () => log('并发达上限被拒'));
// 健康检查
breaker.on('healthCheckFailed', () => log('健康检查失败'));
breaker.on('shutdown', () => log('熔断器关闭'));- 监控集成:
opossum-prometheus模块把熔断器状态导出 Prometheus 指标(状态、调用数、错误率、熔断次数),接入 Grafana 做大盘。 - 告警规则:监听
open事件 → 触发告警(邮件/钉钉/PagerDuty)——熔断器打开意味着下游出问题,运维要介入。
下一步
熔断器原理讲完后,下一步是容错模式与对比——超时、重试、限流、舱壁隔离如何与熔断器配合,以及 Opossum 与 circuit-breaker-js、Resilience4j 的对比。