Java

单接口变慢和整个服务变慢,入口完全不是一回事

值班群里先报的是 `/order/confirm` 超时,但把 URI 看板拆开 30 秒后,`/order/detail`、`/order/list`、`/order/coupon` 也在一起抬头。就是这 30 秒,决定了后面先查连接池还是先查那条接口自己的 SQL。

  • 接口慢
  • 性能排查
  • Java
  • 线程池
  • 系统稳定性
15 分钟阅读

15:41,值班群里丢来一句话:/order/confirm 超时了。”

这句话如果直接接住,很容易马上去翻这条接口的 SQL、线程栈、发布记录,默认它是“单接口慢”。但那天真正改变排查顺序的,不是某个技术细节,而是我先花了 30 秒把 URI 看板拆开。

当时看到的是这组数:

URI平时 P9915:41 P99备注
/order/confirm180ms2.4s告警最先打出来
/order/detail120ms1.1s无告警,但已经抬头
/order/list90ms860ms同步抬头
/order/coupon140ms1.3s同步抬头

再往下看两块共享指标:

指标平时15:41
Hikari pending0-268
DB 平均 RT12ms140ms
Tomcat busy threads34182
inventory-service 调用 RT80ms85ms

看到这里,这件事就不再是“某条接口单独慢了”,而是有人先从 /order/confirm 感知到,实际上整个 order-service 已经在一起变差

也就是从这一眼开始,后面的排查顺序完全改了。

为什么这一步要先做,而不是先翻某条 SQL

因为“只报了一个接口”不等于“只有一个接口有问题”。

线上很多服务,最先出声的往往是流量最大、最敏感、最接近交易闭环的那条 URI。它只是先响了,不代表根因就长在它自己体内。

那天如果把 /order/confirm 当成局部坏路径,排查动作很可能会变成:

  • 先看确认单接口最近改了什么
  • 先追它独有的 SQL
  • 先追它独有的下游

这些动作不能说错,但在那次现场里,优先级都排后了。因为共享层的信号已经比它更硬:

  • 多个 URI 一起抬头
  • 连接池 pending 同时上升
  • 公共数据库 RT 一起变差
  • 并没有某个独有下游先炸

这组证据足够说明:入口该从共享层进,不该从单接口细节进。

那 30 秒之后,动作顺序为什么会反过来

如果把它当成单接口问题,顺序通常是这样的:

  1. 看这条接口独有的代码分支
  2. 看它独有的 SQL 和参数样本
  3. 看它独有的下游
  4. 最后再看共享层有没有被拖住

但那天看到的是服务级形态,所以动作变成了:

  1. 先确认数据库、连接池、公共线程池有没有一起坏
  2. 再看是不是某个共享依赖把多数接口都拖慢了
  3. 最后才回到 /order/confirm 这条链,判断它为什么最先告警

后来果然在共享层先抓到了线索:一个报表批任务在 15:38 开始扫订单表,数据库连接池很快被挤满,/order/confirm 只是因为流量最大、事务最重,最先把问题放大给人看见。

如果一开始就顺着“confirm 接口慢”往里钻,前二十分钟基本都会耗在枝节上。

真正的单接口慢,现场长得不会是刚才那样

为了说明这个分叉到底有多关键,可以把另一类现场放到一起看。

假设面板长这样:

URI平时 P99当前 P99
/order/confirm180ms2.2s
/order/detail120ms130ms
/order/list90ms95ms
/order/coupon140ms150ms

共享层指标还是这样:

指标平时当前
Hikari pending0-21
DB 平均 RT12ms15ms
Tomcat busy threads3440

这才更像真正的“单接口慢”。

这种情况下,顺序就该反过来:

  • 先拆 /order/confirm 的请求样本
  • 先找它独有的 SQL、锁、事务或下游等待
  • 先看是不是某个参数组合把它带进了重路径
  • 共享层只做兜底确认,不是主战场

也就是说,“单接口慢”和“整个服务慢”不是知识点分类题,而是入口分诊题。

入口一旦分错,后面看见的每一份证据都会被误读。

我现在更信哪几块板子

真正到值班现场,最值钱的往往不是长篇分析,而是三块面板:

1. URI 拆分板

先看同一时间窗里,有多少 URI 一起抬头。

这是判断“局部坏路径”还是“共享层先坏”的第一刀。没有这一步,后面所有讨论都悬空。

2. 共享资源板

主要看:

  • 连接池 pending
  • 数据库 RT
  • 公共线程池 queue 或 active
  • 公共下游 RT

如果这些东西同时涨,单接口的局部细节就不该排在最前面。

3. 实例分布板

同一个 URI 是所有实例一起慢,还是只有部分实例慢,也会继续改写顺序。

  • 所有实例一起慢:更像共享层
  • 少量实例慢:更像实例差异、节点资源、配置来源或热点分布

这块板子的价值,在于它能把“全服务慢”再继续切一刀,不让问题停留在大词上。

那天最后得到的教训很简单

用户、业务方、监控系统,都会优先报出最显眼的那个接口。排查的人不能也跟着只盯那一个名字。

先分清这是“单接口自己的坏路径”,还是“整个服务已经被同一个共享原因拖慢”,后面的顺序才会对:

  • 前者去追参数、SQL、锁、独有下游
  • 后者先追数据库、连接池、公共线程池、共享依赖

那天真正省下时间的,不是某条经验口诀,而是 30 秒里把 URI 看板和共享指标并在一起看了一眼。