Java

熔断已经打开了,为什么系统还是要慢很久才缓过来?

熔断打开后系统没有立刻恢复,很多时候不是熔断失效,而是旧请求、重试回流和半开放量还在同一条时间线上继续拖慢恢复。把这段恢复时序看清,才知道系统到底卡在哪一步。

  • 熔断
  • 稳定性治理
  • 超时
  • 重试
  • 调用链
9 分钟阅读

很多人对熔断有一个很自然的想象:

  • 熔断开了
  • 新请求被挡住了
  • 系统就该很快恢复

但真到线上,最让人烦躁的往往就是这一段:

  • 熔断状态已经 open 了
  • 降级也开始生效了
  • 可 RT 还是高
  • 线程池和连接池还是紧
  • 504 和 timeout 也没有立刻掉回去

于是现场经常会冒出一句话:

  • 熔断不是已经开了吗,怎么还没好?

这句话的问题在于,它把“熔断打开”和“系统恢复”想成了同一个时间点。真实系统里,这两件事之间通常隔着一整段恢复时序。

所以比起反复争“熔断到底生没生效”,我更愿意先把这段时间线拉出来看。

一个很典型的恢复时间线

如果把很多事故里最常见的恢复过程压成一条线,往往会是这样。

第一分钟:熔断打开,新增直连流量开始下降

这一步通常是真的。

熔断器开始拒绝一部分请求,或者直接走降级路径。单看状态位,你会觉得保护动作已经启动了。

但这一步切掉的,主要只是“后面再进来的新增请求”。

第二分钟:旧请求还在系统里没出来

这是很多人低估的一段。

在熔断真正打开之前,系统里往往已经压了不少东西:

  • 线程池里排着的请求
  • 已经发出去但还在等 timeout 的调用
  • 正在等数据库连接的事务
  • 业务线程里还没完成的 future

这些不会因为熔断状态变了,就瞬间消失。

所以你会看到一种很别扭的现象:

  • 新增流量已经在降
  • 但 RT 和线程占用还是高

这时候如果有人因为“熔断已开”就断言系统应该立刻恢复,判断通常会偏。

第三分钟:上游重试和补偿开始回流

这又是第二个常见拖慢器。

很多链路不是你这边熔断了,整条链上的额外请求就同步停下来的。

现实里很常见的是:

  • SDK 还在重试
  • 网关还在重放
  • 补偿任务还在继续
  • 失败回查又把一些调用重新推回来

于是你看到的是:

  • 新增直连流量虽然降了
  • 但系统里还是不断有失败后的回流压力

它不一定像事故最开始那样陡,但足够把恢复继续拖慢。

第四分钟:下游刚缓一点,half-open 又把流量放回去

这一步最容易形成来回振荡。

很多系统在半开阶段,只要探测请求成功了几次,就会比较激进地把流量放回去。问题是,下游这时往往只是“刚缓过一口气”,并不等于已经恢复到稳定承载状态。

于是现场就会出现:

  • 刚看到一点恢复迹象
  • 放量
  • 又被重新压慢
  • 熔断再次打开

从外面看,就像熔断器一直在工作,但链路就是迟迟回不到健康状态。

第五分钟以后:降级路径本身也在消耗资源

还有一种恢复慢,不是因为下游还在被打,而是因为降级路径自己也不轻。

比如熔断后虽然不再真正访问下游,但业务仍然要:

  • 查本地或其他兜底数据
  • 组装复杂返回对象
  • 打大量失败日志和埋点
  • 执行失败后的补偿逻辑

这时你会发现:

  • 下游流量确实降了
  • 但应用线程和部分资源并没有像预期那样快速松下来

所以恢复慢,不一定是熔断没生效,也可能是熔断后的系统仍然在付出不小的处理成本。

为什么把它写成时间线,比写成一套完整排障课件更有用

因为很多现场真正卡住的,不是“大家不知道有哪些可能性”,而是没人把恢复理解成一个过程。

大家太容易盯着某一个时刻下结论:

  • 熔断开了,所以应该好了
  • 下游调用降了,所以应该好了
  • 有一波请求成功了,所以应该放量

但恢复不是开关,它更像排空一段已经积起来的交通堵塞。

新增车流被挡住,只说明入口在收;路上已经堵着的车、回头车、旁路车,还得一段一段消掉。

把这件事看清,很多争论会自然消失。

我会怎么压这个判断

以后如果再碰到“熔断已经开了,系统怎么还没恢复”,我更愿意先按这条线想:

  • 先看新增压力是不是真的切住了
  • 再看旧请求是不是还在慢慢出清
  • 再看重试和补偿有没有继续回流
  • 最后看 half-open 和降级路径是不是又把恢复拖慢了

这四步比单看一个熔断状态位,更接近真实系统的恢复过程。

最后压成一句话

熔断打开,只是系统开始恢复,不是系统已经恢复。

很多时候真正拖慢恢复的,不是熔断器本身,而是旧请求没出清、重试还在回流、half-open 又过早放量,以及降级路径自己也在继续吃资源。

把这段时间线看清,才知道系统为什么会“明明已经保护了,却还是慢很久”。