上一题下一题

结合你做过的 Java 后端项目,说明一次接口从慢到稳定的优化过程:如何定位瓶颈、验证方案并沉淀监控指标?

免费版
来源:Java 后端新手村高级工程师
答案解析

我之前在一个订单查询类接口上做过一次优化,这个接口主要给运营后台使用,按用户、订单状态、时间范围分页查询订单列表,并且会返回一些商品、支付、物流相关信息。

当时问题是接口在数据量上来之后不稳定,平时 P95 大概 800ms 左右,高峰期会到 3 到 5 秒,偶尔还会超时。我们没有一上来就改代码,而是先确认慢在哪里。

定位时主要分了几层来看:

先看网关和应用监控,确认慢请求集中在这个接口,不是整体机器 CPU、内存、GC 或网络抖动导致的。 然后通过链路追踪看一次请求的耗时分布,发现主要耗时集中在数据库查询和后续组装数据两块。 再看应用日志里的慢 SQL 和 MySQL slow log,发现核心订单表查询在某些条件下走了 filesort,分页越靠后越慢。 同时通过 Arthas 和线程栈看了一下,接口里还存在循环查询商品和物流信息的问题,也就是典型的 N+1 查询。

数据库这块,原来的 SQL 大概是按时间范围、状态、用户 ID 查询,然后 order by create time desc limit 分页。问题在于索引设计不匹配实际查询条件,有些组合条件没走到合适索引。我们根据业务最常用的查询维度,加了组合索引,比如 status + create time、user id + create time 这类,并且调整了 SQL 条件顺序和返回字段,避免 select 。对于运营后台深分页场景,我们也做了限制和优化,普通查询只允许翻到一定页数,导出走异步任务,不再直接同步查大分页。

数据组装这块,原来是查出订单列表后,循环调用商品服务、支付服务、物流服务。优化后改成批量查询,比如先拿到订单 ID、商品 ID,再一次性批量查商品和物流信息,然后在内存里用 Map 做关联。对于一些变化不频繁的数据,比如商品基础信息,也加了 Redis 缓存,设置合理过期时间,并避免缓存穿透,比如空值短暂缓存。

方案验证上,我们没有直接全量上线。先在测试环境用接近生产的数据量压测,对比优化前后的 TP90、TP99、数据库 QPS、慢 SQL 数量。上线时走灰度,先放一部分用户和运营账号,同时观察接口耗时、错误率、数据库 CPU、连接池使用率、Redis 命中率这些指标。确认没有异常后再逐步放量。

优化后的效果比较明显,接口 P95 从 800ms 左右降到 200ms 到 300ms,高峰期 P99 也稳定在 1 秒以内,慢 SQL 基本消失,数据库连接池等待也少了很多。

后面我们把这次优化沉淀成了几类监控指标:

接口层:QPS、平均耗时、P95/P99、错误率、超时率。 数据库层:慢 SQL 数量、SQL 执行耗时、扫描行数、连接池活跃连接数和等待时间。 缓存层:Redis 命中率、耗时、失败率、热点 Key 情况。 JVM 和机器层:CPU、内存、GC 次数和耗时、线程池队列长度。 业务层:订单查询量、导出任务量、分页深度分布。

同时我们也加了告警,比如 P99 连续几分钟超过阈值、错误率升高、慢 SQL 突增、线程池队列积压等都会通知到负责人。我的理解是,接口优化不能只看某一行代码,还是要从链路、SQL、缓存、并发模型和业务使用方式一起看,并且优化结果一定要用数据验证,最后沉淀到监控和告警里,否则问题很容易反复出现。