熔断器基本原理与状态机

熔断器这东西,最早是从电路保护里借来的概念。你想想看,家里的电闸跳了,是因为电流过大烧保险丝。换成微服务,就是当调用链路出现大量失败或超时时,主动把“开关”断开,别让请求继续过去添乱。

我刚开始接触熔断降级时,总觉得这玩意儿跟限流差不多。后来在线上踩过坑才明白——限流是保护自己不被撑爆,熔断是保护调用方不被拖死。两码事。

状态机:三个状态搞定一切

几乎所有成熟的熔断器实现,都遵循一个三状态模型:

  • 关闭(Closed)——正常干活,请求照常放行。同时偷偷统计失败率。
  • 打开(Open)——熔断了,直接拒绝请求,快速失败。给你一个喘息窗口。
  • 半开(Half-Open)——试探阶段,放少量请求过去看看服务恢复了没。

状态流转不是随便跳的,有明确条件。我把这套逻辑画成了一张图,你一看就明白。

熔断器三状态机 CLOSED 正常调用·统计失败率 OPEN 直接拒绝·快速失败 HALF-OPEN 试探放量·等待结果 失败率 ≥ 阈值 超时窗口到期 试探成功 试探失败 关键参数:失败率阈值 · 熔断超时 · 半开请求数 · 统计窗口大小

状态转换的详细条件

光看状态机图还不够,咱们得把每个跳转条件掰扯清楚。

转换 触发条件 典型参数 我的备注
Closed → Open 统计周期内失败率超过阈值 失败率 > 50%
至少请求数 > 10
阈值别设太低,否则容易误触
Open → Half-Open 熔断超时窗口结束(时间到达) 超时=5s ~ 30s 我一般用 10s,抖动场景可加长
Half-Open → Closed 试探请求全部成功 试探数=3,成功率=100% 通过后立即关闭,不必等窗口
Half-Open → Open 试探请求中有一个失败 重新计算超时窗口 注意:失败就立刻回 Open,不等

小技巧:我曾在一个高并发支付项目里,把半开试探数从1改成3。结果发现3个请求中偶尔有一个超时,其他俩成功,结果又回到了Open。后来改成“半数成功即可”的策略,稳定多了。

统计窗口:熔断的“记忆”有多大

熔断器不是把失败率一直累加,那样老数据会影响判断。一般用滑动窗口统计——留最近一段时间的数据。

  • 时间窗口(Time Window):比如统计过去10秒内的请求。10秒一过,旧数据就丢。
  • 计数窗口(Count Window):统计最近N次请求。N设定好,新旧交替。
  • 混合窗口:同时限制时间和次数,取其中。

我个人习惯用时间窗口,因为线上调用量变化大,单纯按次数容易在小流量时误判。

注意坑:窗口太小容易频繁触发熔断,窗口太大又反应迟钝。我曾经把窗口设成60秒,结果服务挂了一分钟熔断器都没动作——那叫一个惨。

恢复机制的两种流派

状态机里的 Half-Open 就是恢复入口。但具体怎么恢复,业界有两种主流做法:

  1. 被动恢复(超时驱动):Open 状态保持固定时间,时间一到自动切到 Half-Open 试探。常用方案。简单,但固定时长不灵活。
  2. 自适应恢复(指数退避):每次失败后,下次超时窗口翻倍。比如第一次5s,第二次10s,第三次20s……

我做过一个对比测试:在流量抖动频繁的场景下,自适应恢复比固定超时减少了30%的误熔断。代价是代码复杂一点。

// 自适应恢复的核心逻辑(伪代码)
if currentState == OPEN:
    backoffDuration = baseTimeout * (2 ^ failCount)
    if elapsedSinceFuse > backoffDuration:
        transitionTo(HALF_OPEN)
    else:
        rejectRequest()

核心结论:状态机是熔断器的骨架,统计窗口是血肉,恢复策略是神经。三者配合不好,熔断就会变成“阉割”或者“暴毙”。

熔断器 vs 断路器:别混了

你可能会在资料里看到“断路器”这个词。其实一回事,英文都是 Circuit Breaker。但在中文语境里,微服务界说“熔断器”更多,电路界说“断路器”更多。我讲课都统一叫熔断器,省得混淆。

最后送你一句我自己的总结:熔断器不是降级本身,它是降级的开关。开关坏了,降级就是空话。