Java

最近一次发布后接口变慢,别急着追某个 commit,先把发布窗口里的变化摊平

19:02 发布完成,19:09 `/trade/confirm` 的 P99 从 180ms 顶到 2.1s。后来把 19:00 到 19:15 的发布窗口摊开看,先跳出来的不是某个 commit,而是灰度开关和紧跟着启动的回填任务。

  • 接口慢
  • 发布排查
  • 故障排查
  • 变更管理
  • Java
17 分钟阅读

那天值班群里第一句是:“刚发完版,/trade/confirm 慢了,先看哪个 commit?”

我没急着开代码页,先把 19:00 到 19:15 这段发布时间窗里的东西拉到一张表里。表一出来,方向就变了。

时间事件现场信号
19:00开始发布 trade-service v2026.03.24.3老实例流量正常
19:02新版本 6 个 Pod 全部 Ready/trade/confirm P99 仍在 180ms 左右
19:04灰度流量从 10% 放到 30%只有新 Pod 的该接口 RT 开始抬头,老 Pod 仍稳定
19:08发布后任务 coupon-eligibility-backfill 自动启动数据库连接池 pending 从 0 涨到 37
19:09/trade/confirm P99 180ms → 2.1s新 Pod 更明显,超时开始出现
19:12暂停回填任务P99 快速回落到 320ms
19:15关闭新逻辑开关 coupon.enrich.enabled恢复到 200ms 内

这张表最有用的地方,不是“证明谁有锅”,而是把一个模糊的“发布后变慢”拆成了两段:

  • 19:02 版本发完时,其实还没慢
  • 19:08 之后,随着回填任务和新开关一起生效,慢点才真正站起来

如果一开始就只盯 git diff,这 6 分钟的差别很容易被抹掉。

先查什么,取决于慢点是跟着哪一分钟长出来的

这次现场里,真正有价值的第一问不是“改了哪段代码”,而是:

接口到底是在“发布完成”那一刻变慢,还是在“发布窗口里的另一个动作”发生后变慢?

这不是咬文嚼字。因为它直接决定你先翻哪一层证据。

上面这张时间表已经告诉我三件事:

  1. 不是所有变更都在 19:02 同时见效
  2. 新老实例分叉很明显,说明现场不只是代码本身
  3. 数据库等待是在发布后任务启动时一起冒出来的

到这一步,排查顺序已经不该是“先翻 commit → 再看别的”,而应该变成:

  • 先对齐发布时间窗里的动作顺序
  • 再看新实例和老实例的分叉
  • 最后再决定要不要往代码细节里钻

这次真正把人带偏的,是“发布”两个字太大了

群里一开始说“发布后慢了”,大家脑子里默认都是同一个画面:

发版 → 某段新代码变慢 → 找出 commit

但真实现场不是这么单线条。这个发布包里一起动了四样东西:

  • 新代码
  • 新配置开关
  • 新 Pod 批次
  • 发布后自动触发的回填任务

其中最容易被漏掉的,反而是后两样,因为它们不在 git diff 里。

当时顺手拉了一眼配置中心的变更记录,能看到一个很关键的开关在新版本窗口里一起开了:

-coupon.enrich.enabled=false
+coupon.enrich.enabled=true

这个开关本身不一定单独把接口打慢,但它让 /trade/confirm 在结算时多了一次优惠资格补查;而 19:08 启动的回填任务也在扫同一批表。两个动作叠在一起,才把等待链拖长。

所以这次不是“代码没问题”,而是把“发布”误读成“只有代码在变”,才让第一轮判断走偏了。

新老实例分开看,价值往往比先翻代码还高

19:04 灰度流量放大后,面板很快出现了这种形状:

实例/trade/confirm P95错误率备注
trade-6f8d9 old-1210ms0.1%老版本
trade-6f8d9 old-2230ms0.1%老版本
trade-a13c2 new-11.4s1.9%新版本
trade-a13c2 new-21.7s2.4%新版本

只要看到这种新老分叉,脑子里就不能只剩“是不是代码回归”这一条路了。

因为新 Pod 比老 Pod 多出来的,不只是二进制包,还可能有:

  • 配置来源不同
  • 环境变量不同
  • 节点资源不同
  • 路由样本不同
  • 启动后自动任务不同

这次现场里,继续往下拉日志就能接上:

19:08:11  INFO coupon-eligibility-backfill start batchSize=5000
19:08:34  WARN hikari-pool pending=37 active=64 max=64
19:09:02  WARN /trade/confirm cost=2148ms traceId=9f7c... pod=trade-a13c2-new-2

时间线、实例分叉、连接池日志,三样一拼,比单看代码 diff 更快把范围缩住。

这种发布窗口里,最该优先拉平的是哪几类变化

这次过后,我对“发布后接口慢先查什么”这件事,判断更明确了:

第一类:时间线

先把下面这些时间点放在同一条线上:

  • 发布开始和完成
  • 灰度开始和放量
  • 配置开关生效
  • 后台任务启动
  • 指标抬头
  • 人工动作(暂停任务、关开关、回滚)

如果没有这条线,后面很多讨论都只是印象流。

第二类:实例分叉

别急着用“全服务变慢”这种大词。先问一句:

  • 是所有 Pod 一起坏,还是新 Pod 先坏?

新老实例一旦分叉,说明现场可以继续往配置、环境、路由和任务上切,不用一头扎进代码细节里盲找。

第三类:发布包之外的伴随动作

很多慢点根本不是版本一 Ready 就出现,而是跟着这些动作站起来:

  • 预热
  • 回填
  • 索引重建
  • 规则同步
  • 灰度放量

这次最说明问题的,不是改动有多大,而是暂停回填后 RT 先回落。这比“猜测某个 commit 可疑”更接近现场事实。

回到标题里的那个问题:发布后接口慢,先查什么

如果只能先做一个动作,我会选:

先把发布窗口里的分钟级时间线、实例分叉和伴随动作摊到一张图上。

原因很简单。发布不是一个点,而是一串动作。接口慢也不是一句话,而是某一批实例、某一分钟、某一层等待先开始变差。

把这三件事先对齐,后面你再去看 commit、配置 diff、线程池、SQL,证据才会往同一个方向收。

这次现场最后当然也翻了代码,但真正把方向扳正的,不是代码页,而是那张 19:00 到 19:15 的发布窗口表。