1. 场景:促销活动的崩溃
接到报警短信,核心接口响应时间突破5秒 ,DB CPU飙到100%。
用Arthas抓取线上火焰图后发现:
---[ 4763ms ] com.example.service.OrderService.createOrder()
|---[ 98% ] com.example.mapper.OrderMapper.insert() # MySQL写入
|---[ 1.5% ] RedisUtils.get() # 缓存查询
结论:
- 每秒8000+订单直接打穿MySQL
- 分布式锁使用不当导致线程堆积
2. 第一阶段优化:MySQL突围战
2.1 从全表扫描到索引优化
原SQL(执行时间1.2s):
SELECT * FROM orders WHERE user_id=123 AND status IN (1,2,3) ORDER BY create_time DESC;
优化方案:
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status); # 联合索引
EXPLAIN SELECT id FROM orders WHERE user_id=123 AND status=1; # 确认索引命中
效果 :单次查询从1200ms → 8ms
2.2 从单条insert到批量插入
原始代码:
for (OrderItem item : items) {
orderItemMapper.insert(item); // 循环插入
}
优化代码:
orderItemMapper.batchInsert(items); // MyBatis批量插入
XML配置:
<insert id="batchInsert">
INSERT INTO order_item VALUES
<foreach collection="list" item="item" separator=",">
(#{item.id}, #{item.orderId}, ...)
</foreach>
</insert>
效果 :1000条数据插入从12s → 0.8s
3. 第二阶段优化:Redis缓存设计
3.1 缓存击穿解决方案
问题场景 :热点商品缓存失效瞬间,5万QPS直接打穿DB
解决方案:
public Product getProduct(Long id) {
// 1. 尝试从缓存获取
String key = "product:" + id;
Product product = redisTemplate.opsForValue().get(key);
if (product != null) return product;
// 2. 获取分布式锁(防止并发重建缓存)
String lockKey = "lock:" + key;
try {
if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
// 3. 二次检查缓存(防止其他线程已写入)
product = redisTemplate.opsForValue().get(key);
if (product != null) return product;
// 4. 查数据库并写入缓存
product = productMapper.selectById(id);
redisTemplate.opsForValue().set(key, product, 5, TimeUnit.MINUTES);
return product;
} else {
// 5. 未抢到锁的线程短暂休眠后重试
Thread.sleep(100);
return getProduct(id);
}
} finally {
redisTemplate.delete(lockKey);
}
}
3.2 大Value拆分
问题发现 :Redis内存报警,某个2MB的缓存Key导致慢查询
优化方案:
-
将商品详情拆分为基础信息 (高频访问)和扩展信息(低频访问)
-
采用Hash结构存储而非JSON序列化
// 存储
redisTemplate.opsForHash().putAll("product:base:"+id, Map.of(
"name", product.getName(),
"price", product.getPrice()
));
redisTemplate.opsForHash().putAll("product:ext:"+id, Map.of(
"description", product.getDescription(),
"spec", product.getSpec()
));// 查询
Map<String, Object> base = redisTemplate.opsForHash().entries("product:base:"+id);
4. 第三阶段优化:JVM调优
4.1 GC日志分析
在启动参数中添加:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/logs/gc.log
通过GCViewer分析发现:
- Young GC频率高达5次/秒
- 老年代使用率持续90%+
4.2 参数调整
原配置:
-Xms1g -Xmx1g -XX:NewRatio=2
优化后:
-Xms4g -Xmx4g -XX:NewRatio=1 -XX:SurvivorRatio=8 -XX:+UseG1GC
效果:
- Young GC频率降至0.2次/秒
- 接口TP99从200ms → 80ms
5. 最终效果对比
指标 | 优化前 | 优化后 |
---|---|---|
接口RT | 400ms | 20ms |
MySQL QPS | 3000 | 800 |
Redis命中率 | 70% | 99.8% |
GC停顿时间 | 200ms/次 | 50ms/次 |
6. 血泪教训
- 不要相信本地测试:压测要用生产级数据量
- 监控比优化更重要:提前部署Prometheus+Grafana
- 分布式锁要设超时:避免死锁导致线程池爆炸
- Key命名要规范 :建议
业务:类型:ID
三段式