接口偶发性变慢,但数据库和服务器都很正常,问题到底出在哪?

这是一个我在真实项目中遇到、也见过很多团队长期“带病运行”的问题:

线上接口偶尔会突然变慢,但 CPU、内存、数据库监控全部正常。

慢的时候很慢, 正常的时候又一切如常。


一、问题是怎么出现的(真实背景)

当时的技术背景是:

  • Java 后端服务
  • Spring Boot + REST 接口
  • MySQL 数据库
  • 日均访问量不高
  • 部署在普通云服务器

问题表现为:

  • 大多数请求:几十毫秒
  • 偶尔请求:2~5 秒
  • 无报错、无异常
  • 重试后往往又恢复正常

监控上看到的情况是:

  • CPU 正常
  • 内存充足
  • 数据库 QPS 很低

二、为什么这个问题会卡住(难点在哪里)

这个问题非常“磨人”,原因在于:

  1. 问题不稳定

    • 很难复现
    • 本地测不出来
  2. 监控数据没有异常

    • 看不出瓶颈
  3. 直觉会误导方向

    • 很容易往“数据库慢”“GC 问题”上想

于是它变成了一种典型的:

看起来像性能问题,但又不像性能问题的问题。


三、错误尝试 / 排除过程(真实经历)

最开始,我按常规思路排查过:

❌ 1. 怀疑数据库慢查询

  • 打开慢 SQL 日志
  • 查看执行计划

结果: 👉 没有慢 SQL


❌ 2. 怀疑 JVM GC

  • 打 GC 日志
  • 看 Full GC 情况

结果: 👉 GC 非常稳定


❌ 3. 怀疑网络波动

  • Ping
  • Traceroute

结果: 👉 没有明显异常

这些排查虽然没解决问题,但至少确认了一点:

慢,不是算力问题。


四、关键转折点(问题真正被看见的时刻)

真正的转折点,出现在一次接口耗时拆分上。

我把一个接口的耗时,简单拆成三段:

  1. 接口进入
  2. 业务逻辑执行
  3. 接口返回

通过日志发现:

业务逻辑几乎瞬间完成, 慢全部发生在“接口返回之前”。

这说明:

  • 不是数据库
  • 不是业务代码
  • 而是在响应阶段被阻塞了

五、真正的原因(工程层面的解释)

继续向下查,最终定位到一个容易被忽略的点:

HTTP 客户端连接池耗尽

原因链路是这样的:

  1. 接口内部调用了第三方 HTTP 服务
  2. 使用了默认配置的 HTTP 客户端
  3. 连接池大小很小
  4. 在并发稍高时,连接被占满
  5. 新请求阻塞等待连接释放

关键在于:

  • 阻塞不会报错
  • 只会“慢慢等”

于是就出现了:

接口偶发性变慢,但资源一切正常


六、解决方案(能直接用)

✅ 方案 A:显式配置 HTTP 连接池(推荐)

明确设置:

  • 最大连接数
  • 单路由最大连接数
  • 连接超时

示例(思路级):

1
2
3
4
maxTotalConnections = 200
maxPerRoute = 50
connectionTimeout = 2s
readTimeout = 3s

⚠️ 方案 B:减少同步外部调用(结构性优化)

  • 异步化
  • 降级处理
  • 超时快速失败

适合对稳定性要求更高的场景。


七、验证结果(用事实说话)

修改后:

  • 接口耗时恢复稳定
  • 再未出现 2~5 秒的异常慢请求
  • 并发下表现可预期

问题彻底消失。


八、这个问题带来的“可复用收益”

这个问题,给我留下了一个非常重要的判断经验:

✅ 1. 资源正常 ≠ 系统没有阻塞

  • 阻塞往往发生在等待上
  • 而不是计算上

✅ 2. 偶发慢,优先怀疑“池”

包括但不限于:

  • 线程池
  • 连接池
  • HTTP 客户端池

✅ 3. 学会拆解耗时,而不是盯总耗时

只看“慢”,永远查不清。


九、用「问题+,答案++」回看全流程

  • 问题:接口偶发性变慢
  • 答案:HTTP 连接池耗尽导致阻塞
  • ++:形成“慢即阻塞”的排查直觉

结语

这是一个不会写进教程里的问题, 但它会反复出现在真实系统中。

把它记录下来, 下次再遇到类似情况, 你就不会从零开始。

问题+,答案++