Java

接口超时风暴里,先止血还是先定位?如何判断分界线?

超时风暴最难的不是知不知道要止血或定位,而是没看清什么时候已经从“还可以取证”变成“再不减压就会整片扩散”。把影响面、资源水位、放大器和可逆动作放到同一条时间线上,分界线通常会比想象中清楚。

  • 接口超时
  • 故障排查
  • 稳定性
  • 限流降级
  • Java
13 分钟阅读

我见过最难做决定的一次超时风暴,不是在第一批 timeout 出来的时候,而是在第七分钟。

前六分钟里,大家都还能说服自己“再看两眼就能找到根因”。

第七分钟开始,情况突然变了:

  • submitOrder 从单接口慢,扩到 confirmOrderlockCoupon 一起超时。
  • 工作线程 queue 连续两分钟翻倍。
  • 连接池 pending 从几十个涨到几百个。
  • 上游重试把真实调用量顶到了入口流量的 2.4 倍。

值班群里随即分成两派。

一派说先止血,先关重试、先限流,不然系统要被拖穿。

另一派说先别乱动,SQL 还没看清,万一动作把证据打乱,后面更难查。

这类争论表面上是“止血还是定位谁更重要”,其实真正要判断的是另一件事:

现场到底还处在可以先取证的阶段,还是已经进入不先减压就会继续自我放大的阶段。

我后来越来越不把它理解成一个理念问题,而是一个时间窗口问题。

那次值班真正让我们换挡的,不是 timeout 数本身,而是放大器已经起来了

一开始大家盯的还是最显眼的现象:

  • 用户报下单超时
  • 网关 504 在涨
  • 慢 SQL 条数变多

这些都重要,但它们还不足以决定“现在必须先止血”。

真正让决策换挡的,是下面几件事一起出现:

  • 同一批请求开始被重试两次、三次。
  • 排队时间增长速度明显快于真正业务处理时间。
  • 某些实例上的线程和连接已经接近硬上限,但入口流量并没有继续涨太多。
  • 明明根因还没完全确认,外层影响面却已经在自己扩大。

这时我们才意识到,现场已经不是“一个点慢了”,而是系统开始把这点慢放大成一片超时

一旦走到这一步,继续只讨论根因,会越来越像在火势蔓延时继续研究第一簇火苗是怎么点着的。

我现在更愿意用四个问题来切这条分界线

不是抽象地问“止血重要还是定位重要”,而是连续问下面四件事。

第一,影响面是在收,还是还在扩

如果你看到的是:

  • 还停留在单接口、少量实例、局部租户
  • 波动范围没有继续外扩
  • 其余核心接口仍然稳

那通常还说明证据窗口有价值,定位可以放前一点。

但如果你已经看到:

  • 从一条接口扩到一串接口
  • 从局部实例扩到整个服务
  • 从少量超时扩到用户明显感知

那就别再把它当成“慢一点但还能继续看”的普通退化。

影响面如果还在扩,止血优先级通常就已经开始往前走了。

第二,资源水位是在抬一下,还是在逼近上限

我后来很少再只看 timeout 数本身,而是一定会一起看:

  • 工作线程 active 和 queue
  • 连接池 active 和 pending
  • 重试量
  • 网关 504
  • 下游超时预算消耗

因为真正决定你还有多少操作空间的,不是用户已经抱怨多少,而是系统离“再多一点就整片卡死”还有多远。

那次事故里,SQL 其实还没完全查明,但线程和连接已经明显往硬上限顶。到了这个阶段,继续把主要精力放在细扣慢 SQL,就已经不是稳妥,而是拖延。

第三,放大器是不是已经接管了现场

很多根因本身没那么大,真正把事故打穿的是放大器。

典型放大器包括:

  • 同步重试
  • 批任务抢资源
  • 半冷半热实例反复承压
  • 调大 timeout 后请求占用更久
  • 补偿任务把本来局部的问题抬成全局排队

如果这些东西已经同时出现,那就说明你面对的不再只是“根因”,而是“根因 + 系统自激”。

这时先减掉放大器,往往比先争论最底层哪条 SQL 最慢更重要。

第四,现在有没有低风险、可逆、能直接减压的动作

并不是每次都要上大动作。

那次最后先做的并不是重启、扩容、调大连接池,而是两件更克制的事:

  • 先关高风险同步重试。
  • 先暂停一批会打到同一库表的补偿任务。

原因很简单:

  • 它们可逆。
  • 它们减的是真正的放大量。
  • 它们不会把现场一下打乱到完全失真。

这类动作一旦存在,止血就不再是“拍脑袋乱动”,而是有明确收益的优先步骤。

反过来,什么时候还可以先定位

也有一些现场,先止血反而会比先定位更危险。

比如:

  • 只有单接口、少量样本异常,且没有明显扩散。
  • 资源水位还比较低,系统远没到自激阶段。
  • 当前唯一能做的动作是高风险大动作,比如大面积回滚、强制重启、整体扩容。
  • 证据窗口很短,不先抓现场就会彻底丢失。

这时继续取证、先把最早分叉的证据抓住,往往更划算。

说到底,不是“定位总该先”或“止血总该先”,而是:

  • 如果系统还没开始自己放大自己,定位窗口更值钱。
  • 如果系统已经靠重试、排队、占用时间延长在自我加压,减压窗口更值钱。

那次事故里,真正起决定作用的是动作后的回落顺序

最后我们没有在群里继续争论,而是先关了同步重试,暂停了补偿任务,同时保留一组实例继续抓现场。

几分钟后最关键的不是“超时降了没有”,而是回落顺序:

  • 重试量先掉下来。
  • 工作线程 queue 随后回落。
  • 连接池 pending 再跟着下降。
  • 外层 504 和 timeout 才慢慢恢复。

这个顺序很重要。它证明现场当时真正更急的,不是继续深挖最底层 SQL,而是先把已经起来的放大器打掉。

等系统从自激边缘退回来,再去细看 SQL 和下游处理时间,定位空间反而更大。

也正是因为看到了这个回落顺序,我后来才更敢说:

超时风暴里的分界线,往往不在“你知不知道根因”,而在“系统是不是已经开始自己放大自己”。

这篇只讲切分界线,不替你决定具体动作清单

如果你现在已经明确要设计某个止血动作的观察指标、回滚条件和撤回顺序,更该继续看临时止血动作为什么也要记录回滚条件和观察指标?

如果你现在还没分清 timeout 最早落在应用、网络还是下游依赖,也别在这里兜,直接去看接口超时增多时,先区分应用、网络还是下游依赖?

这篇只处理一个现场判断:什么时候还能继续先找根因,什么时候必须先把放大链压住。

我后来最警惕的一句话,是“再看两分钟”

很多超时风暴真正失控,不是因为没人懂技术,而是因为所有人都觉得:

  • 证据已经快够了
  • 再看两分钟应该能定位
  • 现在动手怕把现场打乱

结果这“两分钟”里,重试起来了,排队起来了,连接也被占满了。

等再想止血时,能选的动作已经越来越粗暴。

所以我现在更看重的,不是团队有没有背熟某套止血原则,而是有没有能力在第七分钟之前看出来:

系统现在是在等你定位,还是已经等不起了。