你是否曾遇到这样的情况:系统上线初期运行流畅,但随着用户量增长,响应时间逐渐变长,偶尔还出现超时?上周我们团队就经历了一次典型的性能优化之旅——从数据库慢查询到缓存穿透,再到线程池配置不当,每一步都踩过坑,也总结出了一套行之有效的优化方法。
别担心,性能瓶颈不是“系统原罪”,而是成长的必经之路。今天不讲空洞理论,只分享真实项目中的排查思路+可落地的优化方案。
常见性能瓶颈:这些地方最容易“卡脖子”
数据库慢查询:系统的“慢性病”
-- 问题SQL:未加索引的模糊查询
SELECT * FROM orders WHERE user_name LIKE '%张三%';
现象:
- 查询耗时从50ms飙升到2秒
- 高峰期数据库CPU 90%+
- 用户反馈“页面加载慢”
排查工具:
- MySQL慢查询日志(
slow_query_log=ON) EXPLAIN分析执行计划- Arthas监控SQL耗时
缓存穿透:恶意请求的“隐形炸弹”
场景:
- 用户查询不存在的商品ID(如ID=999999)
- 每次都穿透缓存查数据库
- 10万次请求压垮数据库
排查方法:
- 监控缓存命中率(低于95%需警惕)
- 日志分析高频查询的key
线程池配置不当:资源的“隐形杀手”
// 错误配置:核心线程数过小
new ThreadPoolExecutor(10, 100, 60, SECONDS, new LinkedBlockingQueue<>());
现象:
- 任务堆积在队列
- 响应时间从200ms→5秒
- 线程数监控曲线呈“锯齿状”
消息队列堆积:异步流程的“堰塞湖”
现象:
- RabbitMQ队列深度持续增长
- 消费者处理速度跟不上生产速度
- 业务延迟严重(如订单创建后10分钟才扣库存)
优化实战:四步搞定性能瓶颈
1. 数据库优化:索引+分页+读写分离
-- 优化1:添加复合索引
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
-- 优化2:避免SELECT *
SELECT order_id, amount, create_time FROM orders WHERE user_id = 1001 LIMIT 10;
-- 优化3:大表分页优化(避免OFFSET)
SELECT * FROM orders WHERE create_time > '2023-01-01' ORDER BY create_time LIMIT 10;
效果:
- 慢查询从2秒→50ms
- 数据库CPU从90%→40%
2. 缓存优化:布隆过滤器+空值缓存
// 布隆过滤器防穿透
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 1000000);
if (!bloomFilter.mightContain(productId)) {
return null; // 直接返回,不查数据库
}
// 空值缓存(防恶意攻击)
if (product == null) {
redisTemplate.opsForValue().set("product:" + id, "null", 5, TimeUnit.MINUTES);
}
效果:
- 缓存命中率从85%→99.5%
- 数据库QPS从5000→500
3. 线程池优化:参数调优+监控
// 优化配置(IO密集型)
new ThreadPoolExecutor(
80, // corePoolSize = CPU核心数 × (1 + 等待时间/计算时间)
120,
45,
SECONDS,
new ArrayBlockingQueue<>(160), // 有界队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
监控指标:
- 队列深度(>1000告警)
- 活跃线程数(持续>90%需扩容)
- 任务拒绝次数(>0需优化)
4. 消息队列优化:消费者扩容+批量处理
// 消费者批量拉取(提升吞吐)
@RabbitListener(queues = "order_queue",
concurrency = "3-10", // 动态扩容
prefetch = "50") // 一次拉50条
public void handleOrders(List<Order> orders) {
// 批量处理库存
inventoryService.batchDeduct(orders);
}
效果:
- 消费速度从100条/秒→2000条/秒
- 队列深度从5000→0
实战效果:数据说话
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 页面平均响应时间 | 1.8s | 0.2s | 9倍 |
| 数据库QPS | 8000 | 1200 | 降低85% |
| 缓存命中率 | 85% | 99.5% | +14.5% |
| 大促期间系统可用性 | 95% | 99.95% | +4.95% |
| 服务器成本 | 20台 | 12台 | 降低40% |
避坑指南:血泪教训总结
| 问题 | 错误做法 | 正确做法 |
|---|---|---|
| 慢查询 | 直接加索引 | 先用EXPLAIN分析,再针对性优化 |
| 缓存穿透 | 不处理 | 布隆过滤器+空值缓存 |
| 线程池队列 | 用无界队列 | 用有界队列+监控告警 |
| 消息堆积 | 增加消费者 | 先分析原因(是生产太快还是消费太慢) |
真实案例:
某次大促前,我们发现缓存命中率骤降到70%。排查发现是恶意爬虫刷不存在的商品ID。加了布隆过滤器后,命中率瞬间回到99%,数据库压力骤降。
优化不是“一次性工程”
性能优化不是“修一次就完事”,而是持续迭代的过程:
- 每周分析慢查询日志
- 每月做压测验证瓶颈
- 大促前专项优化
上周优化后,系统扛住了双11 50万QPS的流量,运维小哥终于能安心喝下午茶了。性能优化没有银弹,但有方法论——先监控定位,再针对性优化,最后持续验证。