单接口变慢和整个服务变慢,入口完全不是一回事
值班群里先报的是 `/order/confirm` 超时,但把 URI 看板拆开 30 秒后,`/order/detail`、`/order/list`、`/order/coupon` 也在一起抬头。就是这 30 秒,决定了后面先查连接池还是先查那条接口自己的 SQL。
15:41,值班群里丢来一句话:“/order/confirm 超时了。”
这句话如果直接接住,很容易马上去翻这条接口的 SQL、线程栈、发布记录,默认它是“单接口慢”。但那天真正改变排查顺序的,不是某个技术细节,而是我先花了 30 秒把 URI 看板拆开。
当时看到的是这组数:
| URI | 平时 P99 | 15:41 P99 | 备注 |
|---|---|---|---|
/order/confirm | 180ms | 2.4s | 告警最先打出来 |
/order/detail | 120ms | 1.1s | 无告警,但已经抬头 |
/order/list | 90ms | 860ms | 同步抬头 |
/order/coupon | 140ms | 1.3s | 同步抬头 |
再往下看两块共享指标:
| 指标 | 平时 | 15:41 |
|---|---|---|
| Hikari pending | 0-2 | 68 |
| DB 平均 RT | 12ms | 140ms |
| Tomcat busy threads | 34 | 182 |
inventory-service 调用 RT | 80ms | 85ms |
看到这里,这件事就不再是“某条接口单独慢了”,而是有人先从 /order/confirm 感知到,实际上整个 order-service 已经在一起变差。
也就是从这一眼开始,后面的排查顺序完全改了。
为什么这一步要先做,而不是先翻某条 SQL
因为“只报了一个接口”不等于“只有一个接口有问题”。
线上很多服务,最先出声的往往是流量最大、最敏感、最接近交易闭环的那条 URI。它只是先响了,不代表根因就长在它自己体内。
那天如果把 /order/confirm 当成局部坏路径,排查动作很可能会变成:
- 先看确认单接口最近改了什么
- 先追它独有的 SQL
- 先追它独有的下游
这些动作不能说错,但在那次现场里,优先级都排后了。因为共享层的信号已经比它更硬:
- 多个 URI 一起抬头
- 连接池 pending 同时上升
- 公共数据库 RT 一起变差
- 并没有某个独有下游先炸
这组证据足够说明:入口该从共享层进,不该从单接口细节进。
那 30 秒之后,动作顺序为什么会反过来
如果把它当成单接口问题,顺序通常是这样的:
- 看这条接口独有的代码分支
- 看它独有的 SQL 和参数样本
- 看它独有的下游
- 最后再看共享层有没有被拖住
但那天看到的是服务级形态,所以动作变成了:
- 先确认数据库、连接池、公共线程池有没有一起坏
- 再看是不是某个共享依赖把多数接口都拖慢了
- 最后才回到
/order/confirm这条链,判断它为什么最先告警
后来果然在共享层先抓到了线索:一个报表批任务在 15:38 开始扫订单表,数据库连接池很快被挤满,/order/confirm 只是因为流量最大、事务最重,最先把问题放大给人看见。
如果一开始就顺着“confirm 接口慢”往里钻,前二十分钟基本都会耗在枝节上。
真正的单接口慢,现场长得不会是刚才那样
为了说明这个分叉到底有多关键,可以把另一类现场放到一起看。
假设面板长这样:
| URI | 平时 P99 | 当前 P99 |
|---|---|---|
/order/confirm | 180ms | 2.2s |
/order/detail | 120ms | 130ms |
/order/list | 90ms | 95ms |
/order/coupon | 140ms | 150ms |
共享层指标还是这样:
| 指标 | 平时 | 当前 |
|---|---|---|
| Hikari pending | 0-2 | 1 |
| DB 平均 RT | 12ms | 15ms |
| Tomcat busy threads | 34 | 40 |
这才更像真正的“单接口慢”。
这种情况下,顺序就该反过来:
- 先拆
/order/confirm的请求样本 - 先找它独有的 SQL、锁、事务或下游等待
- 先看是不是某个参数组合把它带进了重路径
- 共享层只做兜底确认,不是主战场
也就是说,“单接口慢”和“整个服务慢”不是知识点分类题,而是入口分诊题。
入口一旦分错,后面看见的每一份证据都会被误读。
我现在更信哪几块板子
真正到值班现场,最值钱的往往不是长篇分析,而是三块面板:
1. URI 拆分板
先看同一时间窗里,有多少 URI 一起抬头。
这是判断“局部坏路径”还是“共享层先坏”的第一刀。没有这一步,后面所有讨论都悬空。
2. 共享资源板
主要看:
- 连接池 pending
- 数据库 RT
- 公共线程池 queue 或 active
- 公共下游 RT
如果这些东西同时涨,单接口的局部细节就不该排在最前面。
3. 实例分布板
同一个 URI 是所有实例一起慢,还是只有部分实例慢,也会继续改写顺序。
- 所有实例一起慢:更像共享层
- 少量实例慢:更像实例差异、节点资源、配置来源或热点分布
这块板子的价值,在于它能把“全服务慢”再继续切一刀,不让问题停留在大词上。
那天最后得到的教训很简单
用户、业务方、监控系统,都会优先报出最显眼的那个接口。排查的人不能也跟着只盯那一个名字。
先分清这是“单接口自己的坏路径”,还是“整个服务已经被同一个共享原因拖慢”,后面的顺序才会对:
- 前者去追参数、SQL、锁、独有下游
- 后者先追数据库、连接池、公共线程池、共享依赖
那天真正省下时间的,不是某条经验口诀,而是 30 秒里把 URI 看板和共享指标并在一起看了一眼。