接口偶发性变慢,但数据库和服务器都很正常,问题到底出在哪?
这是一个我在真实项目中遇到、也见过很多团队长期“带病运行”的问题:
线上接口偶尔会突然变慢,但 CPU、内存、数据库监控全部正常。
慢的时候很慢, 正常的时候又一切如常。
一、问题是怎么出现的(真实背景)
当时的技术背景是:
- Java 后端服务
- Spring Boot + REST 接口
- MySQL 数据库
- 日均访问量不高
- 部署在普通云服务器
问题表现为:
- 大多数请求:几十毫秒
- 偶尔请求:2~5 秒
- 无报错、无异常
- 重试后往往又恢复正常
监控上看到的情况是:
- CPU 正常
- 内存充足
- 数据库 QPS 很低
二、为什么这个问题会卡住(难点在哪里)
这个问题非常“磨人”,原因在于:
-
问题不稳定
- 很难复现
- 本地测不出来
-
监控数据没有异常
- 看不出瓶颈
-
直觉会误导方向
- 很容易往“数据库慢”“GC 问题”上想
于是它变成了一种典型的:
看起来像性能问题,但又不像性能问题的问题。
三、错误尝试 / 排除过程(真实经历)
最开始,我按常规思路排查过:
❌ 1. 怀疑数据库慢查询
- 打开慢 SQL 日志
- 查看执行计划
结果: 👉 没有慢 SQL
❌ 2. 怀疑 JVM GC
- 打 GC 日志
- 看 Full GC 情况
结果: 👉 GC 非常稳定
❌ 3. 怀疑网络波动
- Ping
- Traceroute
结果: 👉 没有明显异常
这些排查虽然没解决问题,但至少确认了一点:
慢,不是算力问题。
四、关键转折点(问题真正被看见的时刻)
真正的转折点,出现在一次接口耗时拆分上。
我把一个接口的耗时,简单拆成三段:
- 接口进入
- 业务逻辑执行
- 接口返回
通过日志发现:
业务逻辑几乎瞬间完成, 慢全部发生在“接口返回之前”。
这说明:
- 不是数据库
- 不是业务代码
- 而是在响应阶段被阻塞了
五、真正的原因(工程层面的解释)
继续向下查,最终定位到一个容易被忽略的点:
HTTP 客户端连接池耗尽
原因链路是这样的:
- 接口内部调用了第三方 HTTP 服务
- 使用了默认配置的 HTTP 客户端
- 连接池大小很小
- 在并发稍高时,连接被占满
- 新请求阻塞等待连接释放
关键在于:
- 阻塞不会报错
- 只会“慢慢等”
于是就出现了:
接口偶发性变慢,但资源一切正常
六、解决方案(能直接用)
✅ 方案 A:显式配置 HTTP 连接池(推荐)
明确设置:
- 最大连接数
- 单路由最大连接数
- 连接超时
示例(思路级):
|
|
⚠️ 方案 B:减少同步外部调用(结构性优化)
- 异步化
- 降级处理
- 超时快速失败
适合对稳定性要求更高的场景。
七、验证结果(用事实说话)
修改后:
- 接口耗时恢复稳定
- 再未出现 2~5 秒的异常慢请求
- 并发下表现可预期
问题彻底消失。
八、这个问题带来的“可复用收益”
这个问题,给我留下了一个非常重要的判断经验:
✅ 1. 资源正常 ≠ 系统没有阻塞
- 阻塞往往发生在等待上
- 而不是计算上
✅ 2. 偶发慢,优先怀疑“池”
包括但不限于:
- 线程池
- 连接池
- HTTP 客户端池
✅ 3. 学会拆解耗时,而不是盯总耗时
只看“慢”,永远查不清。
九、用「问题+,答案++」回看全流程
- 问题:接口偶发性变慢
- 答案:HTTP 连接池耗尽导致阻塞
- ++:形成“慢即阻塞”的排查直觉
结语
这是一个不会写进教程里的问题, 但它会反复出现在真实系统中。
把它记录下来, 下次再遇到类似情况, 你就不会从零开始。
问题+,答案++