Java

流量突增还是代码回归,先沿着时间线、单位成本、实例分布往下拆

接口突然变慢时,最容易浪费时间的不是排查,而是太早把问题说成“流量”或“代码”二选一。先把发布时间、流量爬升点、单位请求成本和新老实例分布放到同一条线上,通常更容易拆出触发器和承压能力变化。

  • 接口慢
  • 流量突增
  • 性能回归
  • 故障排查
  • Java
10 分钟阅读

线上接口一慢,现场如果同时满足两件事,最容易卡在错误的地方:

  • 流量确实在涨
  • 最近也确实刚发布过

很多团队一到这里就会很快站队:

  • 一边说是流量冲上来了,先扩容、限流、削峰
  • 一边说是代码回归了,不回滚只会越拖越坏

但真正值钱的,不是先选边,而是先把事故压回一条硬判断线上:

这次把系统推过线的,是外部触发器突然变强,还是系统自己的单位请求成本已经变贵、承压余量被提前吃掉了。

这两件事经常同时存在,但主次不同,处理顺序就会完全不同。

所以比起先争“锅在哪”,更有用的是先沿着三条证据往下拆:

  • 时间线
  • 单位请求成本
  • 版本 / 实例分布

第一件事:先把时间线排出来,看谁先动

先不要急着看一堆总量图,先把几个关键时间点摆平:

  • 最近一次发布是什么时候
  • 流量是什么时候开始明显抬升的
  • RT、错误率、线程池排队、数据库等待分别是什么时候开始变坏的

这一步的价值很直接:很多争论一对时间线,主次就已经缩小了一半。

常见的几种线索

情况一:发布后很快变差,高峰只是把问题彻底放大。

这通常说明系统承压能力已经先降了,只是低流量时还没完全露出来。等到流量再往上推一截,就直接穿线。

情况二:发布后一直平稳,直到流量冲到某个阈值才突然失稳。

这更像流量是主触发器。系统可能本来就接近边界,但真正把它推倒的是总量或并发跨过了阈值。

情况三:发布后有轻微变差,但现场真正爆炸和流量爬升高度重合。

这类最常见,也最容易吵。因为它往往不是单因子事故,而是“承压能力先掉一点,再被流量补上最后一脚”。

所以看时间线,不是为了把问题粗暴压成二选一,而是先回答:

  • 是不是发布后就已经埋下了性能变化
  • 流量是在什么时候把这个变化放大成事故的

第二件事:看单位请求成本,有没有“同样请求变贵了”

总量上涨本身当然会让系统变难受,但真正区分“只是人变多了”还是“每个人也更贵了”的,通常不是总 QPS,而是单位请求成本。

这里更值得盯的是:在相近流量区间里,新旧时段相比,每个请求到底贵了没有。

可以重点看几类东西:

  • 单请求 RT
  • 单请求 SQL 数量和 SQL 耗时
  • 单请求 RPC / 下游调用耗时
  • 单请求 CPU、内存、对象分配或线程占用时间

如果这些指标在相近流量下明显变坏,基本就不能把问题简单归成“今天流量大”。

因为这说明系统接住同一份流量时,自己先变笨重了。

为什么单位成本比总量更能拆主次

看总量时,现场很容易得出一句正确但没用的话:

“请求确实比平时多。”

这句话通常不假,但它只是在描述触发器,不是在解释为什么系统这么快就扛不住。

更有用的问题是:

  • 以前一万请求能扛住,现在为什么八千就开始抖
  • 以前这个接口高峰 RT 只是涨一点,现在为什么直接把线程池拖满
  • 以前数据库有余量,现在为什么同样业务量就开始慢查询增多

这些答案往往不在“来了多少请求”,而在“每个请求把资源吃成什么样”。

如果单位成本明显上升,那么流量可能只是导火索,真正缩小容量边界的是代码、配置、依赖路径或缓存命中变化。

第三件事:看版本和实例分布,问题是偏新实例还是全局一起坏

时间线和单位成本能帮你看清有没有回归,但现场还需要再补一刀:

这次变坏,是不是明显偏在新版本实例上。

这里重点看:

  • 新版本实例和旧版本实例的 RT、错误率、CPU、线程占用差异
  • 某一批新 Pod / 新节点是否先开始变差
  • 同一时刻所有实例是不是几乎同时一起升高

两种很有代表性的分布

新实例更差,旧实例相对稳。

这通常很像发布回归。哪怕流量也在涨,只要新版本明显更贵,就说明承压能力变化不是均匀发生的。

所有实例几乎同时一起变坏。

这更像共享资源被一起推高了,比如数据库、缓存、消息堆积或上游流量整体放大。

但这里也要防一种假象:

  • 新实例先慢一点
  • 它先把数据库、缓存或下游拖高
  • 共享资源一坏,旧实例后面也跟着一起坏

最后你看到的图像就像“全体一起慢”,可真正起点还是新版本把余量先吃掉了。

所以实例分布不能单独看,要和前面的时间线连起来看:到底是谁先差,谁是后来被拖进去的。

把三条线合起来,才能拆出“触发器”和“承压能力变化”

线上这类事故最常见的误区,是非要先给出一个单选答案:

  • 要么是流量问题
  • 要么是代码问题

但更接近真实现场的问法应该是:

  • 触发器是什么:是流量上来了,还是某个依赖先抖了
  • 承压能力为什么变差:是新版本更贵了,还是系统本来就接近边界

把这两个问题拆开,主次通常就顺了。

例如几种常见结论

结论一:流量是主触发器,单位成本没明显变。

这时优先级通常更偏扩容、限流、削峰、资源保护。

结论二:单位成本明显变贵,新实例更差,流量只是把问题提前暴露。

这时回滚或止损发布通常更靠前,否则你只是拿更多资源去喂一个更贵的版本。

结论三:发布先吃掉部分余量,流量再把系统推过线。

这类需要最克制的判断。因为它不是“谁全责”,而是“先止哪一头更能止血”。有时要先保流量面,有时要先处理版本面,但前提是主会场已经把这两层拆开了。

现场别写成会议,动作也别同时开太多条线

这里当然会涉及协同,但这部分不需要讲得太重。

只要记住一个很实用的原则:在时间线、单位成本、实例分布还没拼起来之前,不要同时把太多互相干扰的动作全开出去。

因为一旦你一边扩容、一边回滚、一边改参数、一边放重试,后面很容易根本看不清到底是哪一步在止血,哪一步在继续放大。

所以这类现场更像是在拆主次,不是在做会议纪要。

最后压成一句话

流量突增还是代码回归,别先急着选立场。

先沿着时间线看谁先动,再看单位请求成本有没有变贵,最后看问题是偏新实例还是全局一起坏。这样通常就能把“触发器”和“承压能力变化”拆开。

拆开之后,再决定先扩容限流还是先回滚,动作才不会打架。