线上接口响应慢,用户抱怨,老板催命。排查半天发现不是单一原因,而是多个小问题叠加。我总结了8个最常见的性能陷阱,看看你中了几个。
1. N+1查询
ORM框架的懒加载是罪魁祸首。查订单列表,每个订单再查用户信息,一次请求打出几百条SQL。数据库连接被占满,响应时间飙升。解决:用JOIN或批量查询,MyBatis的@BatchSize、JPA的EntityGraph都能合并查询。
2. 索引缺失或失效
查询字段没索引,或者对字段用了函数、发生隐式类型转换,导致全表扫描。明明加了索引,WHERE DATE(create_time) = '2024-01-01'却让索引失效。解决:用EXPLAIN分析执行计划,避免在索引列上做运算,注意字段类型匹配。
3. 同步调用外部接口
在请求链路里同步调用第三方API,对方慢你就慢。更糟的是没有超时控制,线程一直阻塞。解决:异步化、设置合理超时、加熔断降级。CompletableFuture或响应式编程能把串行变并行。
4. 返回过多数据
接口一次性返回几千条记录,序列化和网络传输都慢。前端可能只需要前20条。解决:强制分页,只返回必要字段,用DTO代替实体类,避免返回大字段(如文本、图片base64)。
5. 缓存使用不当
没缓存,或者缓存击穿、雪崩。热点key过期瞬间,所有请求打到数据库。解决:合理设置过期时间,加互斥锁或逻辑过期,多级缓存(本地+Redis),热点数据永不过期。
6. 连接池配置不当
数据库连接池太小,请求排队等连接;太大,数据库扛不住。HTTP客户端没复用连接,每次新建TCP。解决:根据QPS和RT计算合理池大小,公式是连接数 = QPS × RT。用HttpClient连接池,别用new URL().openConnection()。
7. 低效序列化与日志
JSON序列化大对象,循环里打日志,日志同步写磁盘。解决:按需序列化,用异步日志(Logback AsyncAppender),避免在循环中打日志,生产环境关闭DEBUG级别。
8. 锁竞争与线程池配置
全局锁导致串行化,synchronized方法里做耗时操作。线程池队列无限大导致任务堆积,核心数设成CPU核数却跑IO密集任务。解决:减小锁粒度,用无锁结构(CAS、LongAdder),IO密集任务线程数设为CPU核数 × (1 + 等待时间/计算时间)。
总结
接口慢往往是多个因素叠加。不要凭感觉优化,先用Arthas、SkyWalking、Prometheus定位瓶颈,再逐个击破。记住:没有测量就没有优化。先解决最大的瓶颈,再验证效果,避免过度优化引入新问题。