项目上线后,发现一个接口比较慢,应该如何排查

可以按一个从外到内的顺序排查:先确认"到底慢在哪里",再一层层缩小范围。别一上来就改代码,不然很容易瞎优化。


一、先确认:是真的慢,还是"感觉慢"

先把问题描述清楚:

  1. 哪个接口慢

    • URL 是什么
    • GET / POST
    • 平均慢还是偶发慢
  2. 慢到什么程度

    • 平均 200ms 还是 3s
    • P95 / P99 是多少
    • 是高峰期慢,还是一直慢
  3. 只有线上慢,还是本地也慢

    • 本地快、线上慢 → 更像环境 / 数据量 / 网络 / 并发问题
    • 本地也慢 → 更像代码 / SQL / 算法问题

二、先看日志,拿到一次完整请求链路

最先做的是给这个接口加好耗时日志,至少知道每一段花了多久。

比如要拆成:

  • Controller 总耗时
  • Service 总耗时
  • SQL 耗时
  • 远程调用耗时
  • Redis 耗时
  • 文件 / 对象存储耗时
  • 第三方接口耗时

你可以先用最朴素的方式打点:

java 复制代码
long start = System.currentTimeMillis();

log.info("开始调用接口, param={}", param);

long t1 = System.currentTimeMillis();
User user = userService.getById(id);
log.info("查询用户耗时={}ms", System.currentTimeMillis() - t1);

long t2 = System.currentTimeMillis();
Order order = orderService.getOrder(id);
log.info("查询订单耗时={}ms", System.currentTimeMillis() - t2);

log.info("接口总耗时={}ms", System.currentTimeMillis() - start);

这样你先能知道:

  • 是数据库慢
  • 还是某个远程服务慢
  • 还是代码里某段逻辑慢

三、重点先怀疑这几类问题

线上接口慢,最常见的其实就几种。

1)SQL 慢

这是最常见的。

排查方式:

看 SQL 日志

打开 MyBatis / JPA 的 SQL 日志,先确认这个接口到底执行了哪些 SQL。

你要关注:

  • 有没有一口气查很多次数据库
    比如循环里查库,形成 N+1 查询
  • 有没有 select *
  • 有没有连表太多
  • 有没有大分页 limit 100000,10
  • 有没有模糊查询 %xx%
  • 有没有没走索引

用 EXPLAIN 看执行计划

把慢 SQL 拿出来执行:

sql 复制代码
EXPLAIN SELECT ...

重点看:

  • type 是不是 ALL

    • ALL 往往是全表扫描
  • key 有没有用到索引

  • rows 扫描行数大不大

  • Extra 里有没有

    • Using filesort
    • Using temporary

看是不是数据量上线后暴露问题

开发环境 1 万条数据没问题,线上 1000 万条就慢了。

所以要判断:

  • 查询条件是否命中索引
  • 索引是否合理
  • 是否需要联合索引
  • 是否需要覆盖索引
  • 是否需要改成游标分页

2)远程调用慢

如果接口里调了别的服务,比如:

  • Feign
  • Dubbo
  • HTTP 第三方接口
  • Python 服务
  • AI 服务

那么慢的可能根本不是你自己的代码,而是下游服务

要看:

  • 调哪个服务慢
  • 每次都慢还是偶发超时
  • 网络连接时间长,还是服务处理时间长
  • 有没有重试,导致一次请求实际发了多次
  • 有没有超时设置过大

比如一个接口:

  • 查数据库 50ms
  • 调库存服务 80ms
  • 调用户服务 60ms
  • 调支付服务 2.5s

那瓶颈显然在支付服务。


3)Redis / 缓存没用好

常见问题:

  • 本来应该先查缓存,结果每次都查数据库
  • Redis key 设计不合理,导致命中率低
  • 缓存穿透 / 击穿
  • 查了 Redis 还要再查多次数据库
  • Redis 本身网络延迟高

要看:

  • 这个接口有没有缓存
  • 缓存命中率高不高
  • 没命中后是否回源很重
  • 是否有热点 key

4)代码逻辑本身慢

有时候不是数据库慢,是你 Java 代码写得重。

比如:

  • 大量 for 循环嵌套
  • 内存里做大集合过滤 / join
  • 重复 JSON 序列化反序列化
  • Bean 拷贝太多
  • 大对象构造很多
  • 正则处理很重
  • 读取大文件 / 图片处理
  • 同步调用串行执行太多步骤

例如一个接口里:

  • 先查 5000 条数据
  • 再在内存里 3 层循环匹配
  • 再一个个查详情
  • 再拼 VO

这种就很容易慢。


5)线程池 / 连接池满了

接口慢不一定是"执行慢",也可能是"排队久"。

常见是这些池子满了:

  • Tomcat 线程池
  • 数据库连接池
  • Redis 连接池
  • HTTP 连接池
  • 业务线程池

现象通常是:

  • 平时不慢,高并发时突然慢
  • CPU 不一定高
  • 请求堆积
  • 超时变多

比如数据库连接池只有 10 个连接,但同时 200 个请求都要查库,那很多请求会等连接,自然就慢了。

要看:

  • 活跃线程数
  • 连接池最大连接数
  • 等待队列长度
  • 有没有拒绝策略触发
  • 有没有连接泄漏

6)锁竞争

如果接口里用了锁,也会慢。

比如:

  • synchronized
  • ReentrantLock
  • 分布式锁
  • 数据库行锁 / 表锁
  • 乐观锁重试太多

尤其是这种场景:

  • 扣库存
  • 下单
  • 抢券
  • 更新同一条记录

如果很多请求同时抢同一资源,就会出现:

  • 等锁
  • 自旋
  • 重试
  • 超时

7)JVM / 机器资源问题

还要看是不是机器本身扛不住。

关注这些指标:

  • CPU 是否打满
  • 内存是否紧张
  • Full GC 是否频繁
  • 磁盘 IO 是否高
  • 网络带宽是否高
  • 容器是否被限流

常见现象:

CPU 高

可能是:

  • 死循环
  • JSON 序列化太重
  • 加解密太多
  • 大量对象创建
  • 算法复杂度高

Full GC 多

可能是:

  • 一次请求创建大量临时对象
  • 返回结果太大
  • 缓存放太多
  • 堆太小

如果 GC 频繁,接口会明显卡顿。


四、推荐你实际排查时的顺序

这是比较实战的一套顺序:

第一步:复现

先找到一个慢请求,确认参数、时间点、耗时。

第二步:看接口日志

看总耗时,拆分每一步耗时。

第三步:看 SQL

把接口涉及的 SQL 全拉出来,重点排查慢 SQL、N+1、没索引、大分页。

第四步:看远程调用

确认是不是 Feign / HTTP / RPC 卡住了。

第五步:看监控

看 CPU、内存、GC、线程数、连接池。

第六步:抓火焰图 / 线程栈

如果还是不清楚,就上工具:

  • Arthas
  • SkyWalking
  • Pinpoint
  • Prometheus + Grafana
  • JProfiler / YourKit

这样可以直接看到热点方法到底在哪里。


五、几个很典型的线上慢接口案例

案例1:SQL 没索引

现象:

  • 本地 50ms
  • 线上 4s

原因:

  • where 条件字段没索引
  • 线上表 500 万数据,全表扫描

解决:

  • 加索引
  • 改 SQL
  • 避免 select *

案例2:循环查库

现象:

  • 查订单列表很慢

代码类似:

java 复制代码
List<Order> orders = orderMapper.list();
for (Order order : orders) {
    User user = userMapper.selectById(order.getUserId());
}

问题:

  • 1 次查订单 + N 次查用户
  • 数据一多直接炸

解决:

  • 改成批量查询
  • 或直接 join

案例3:大分页

现象:

  • 前几页很快,后面越来越慢

SQL:

sql 复制代码
select * from user order by id limit 100000, 10;

原因:

  • offset 越大,扫描越多

解决:

  • 改成游标分页

案例4:下游服务超时

现象:

  • 本服务 CPU 正常,SQL 也不慢,但接口 3 秒

原因:

  • Feign 调用别的服务超时
  • 甚至还配置了重试

解决:

  • 给下游加监控
  • 减少串行调用
  • 设置合理超时
  • 能并行就并行
  • 能降级就降级

案例5:缓存失效

现象:

  • 某个热点接口突然慢

原因:

  • Redis key 过期,大量请求一起打到数据库
  • 缓存击穿

解决:

  • 热点数据不过期 / 逻辑过期
  • 加互斥锁
  • 随机过期时间
  • 多级缓存

六、你在公司里可以怎么回答这个问题

如果面试官问"项目上线后,发现一个接口比较慢,应该如何排查",你可以这样答:

我会先从日志和监控入手,先确认是接口整体慢,还是某一段慢。

第一,定位具体慢请求,查看接口总耗时、发生频率、是否只在线上出现。

第二,拆分链路耗时,重点看数据库查询、Redis、远程调用、文件IO等环节。

第三,重点排查 SQL,查看慢查询日志和执行计划,确认是否存在没走索引、N+1 查询、大分页、连表过多等问题。

第四,如果接口依赖下游服务,我会继续看 Feign 或 HTTP 调用耗时,排除网络延迟、下游超时、重试等问题。

第五,再结合机器监控看 CPU、内存、GC、线程池、连接池是否存在瓶颈。

如果还定位不到,我会使用 Arthas、SkyWalking 这类工具进一步分析热点方法和调用链。

整体上就是按"日志定位 -> 链路拆分 -> SQL/远程调用 -> 系统资源 -> 性能工具"这个顺序逐层排查。

这个回答会比较像真实工作。


七、给你一个最实用的排查口诀

你可以记这个:

先看日志,后看SQL;
再看远程,再看缓存;
再看线程池、连接池;
最后看CPU、GC和锁。


如果你愿意,我可以继续给你写一版更适合面试背诵的 "2分钟标准回答" ,或者直接画一张 "慢接口排查流程图"

相关推荐
yuezhilangniao1 小时前
centos7 yum安装PostgreSQL 15 与运维指南
数据库·postgresql
222you1 小时前
Mysql的索引以及底层的数据结构(面试)
数据结构·数据库·mysql
A10169330711 小时前
Nginx与frp结合实现局域网和公网的双重https服务
数据库·nginx·https
happymaker06262 小时前
JDBC(MySQL)——DAY03(Blob类型,批处理,连接池)
数据库·mysql
Dovis(誓平步青云)2 小时前
《MySQL查询进阶:复合逻辑与多表关联的实战心法》
数据库·mysql
June`2 小时前
mini-redis项目之Resp协议
数据库·redis
lhbian2 小时前
redis分页查询
数据库·redis·缓存
顶点多余2 小时前
Mysql 基本查询详解
数据库·mysql
X-⃢_⃢-X2 小时前
八、Redis之BigKey
数据库·redis·缓存