背景:性能瓶颈的发现
我们的电商系统在用户量达到百万级别后,响应时间从 200ms 飙升到 2s。通过性能监控工具(Prometheus + Grafana)发现,主要瓶颈在:
- CPU 使用率过高(85%+)
- 频繁 Full GC(每分钟 3-5 次)
- 数据库连接池耗尽
- 线程阻塞(数据库查询慢)
第一步:JVM 调优
通过分析 GC 日志,发现主要问题是堆内存分配不合理和垃圾回收器选择不当。
1.1 堆内存调整
原始配置:
-Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
优化后配置:
-Xms4g -Xmx4g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g
-XX:NewSize=2g -XX:MaxNewSize=2g
-XX:SurvivorRatio=8
💡 关键点
- 年轻代设置为堆的 50%(2g),减少对象晋升到老年代
- Eden:Survivor = 8:1,提高年轻代利用率
- 元空间增大,避免类加载过多触发 Full GC
1.2 垃圾回收器选择
从 CMS 切换到 G1:
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=45
效果:Full GC 频率从每分钟 3-5 次降低到每小时 1 次,暂停时间控制在 200ms 内。
第二步:代码层优化
2.1 数据库查询优化
发现N+1 查询问题:
// 优化前:每个订单都查询一次用户信息
List orders = orderMapper.selectByIds(orderIds);
for (Order order : orders) {
User user = userMapper.selectById(order.getUserId());
order.setUser(user);
}
优化后:批量查询
// 优化后:一次性查询所有用户
List orders = orderMapper.selectByIds(orderIds);
Set userIds = orders.stream()
.map(Order::getUserId)
.collect(Collectors.toSet());
Map userMap = userMapper.selectByIds(userIds)
.stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
orders.forEach(order -> order.setUser(userMap.get(order.getUserId())));
效果:数据库查询次数从 1000+ 降低到 2 次,查询时间从 5s 降低到 500ms。
2.2 缓存策略
引入多级缓存:
@Cacheable(value = "user", key = "#userId")
public User getUserById(Long userId) {
return userMapper.selectById(userId);
}
// 使用 Caffeine 作为本地缓存
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10000));
return cacheManager;
}
效果:缓存命中率 85%,数据库压力降低 70%。
第三步:并发优化
3.1 线程池调优
原始配置:
ExecutorService executor = Executors.newFixedThreadPool(10);
优化后:自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
20, // 核心线程数 = CPU 核心数 * 2
50, // 最大线程数 = CPU 核心数 * 5
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
3.2 异步处理
将非核心业务异步化:
@Async("asyncExecutor")
public CompletableFuture sendOrderNotification(Order order) {
// 发送短信、邮件通知
notificationService.send(order);
return CompletableFuture.completedFuture(null);
}
效果:主流程响应时间从 2s 降低到 300ms。
第四步:数据库优化
4.1 索引优化
通过 EXPLAIN 分析慢查询,添加联合索引:
ALTER TABLE orders ADD INDEX idx_user_status_created (
user_id, status, created_at
);
4.2 分库分表
订单表按用户 ID 分片:
spring.shardingsphere.datasource.names=ds0,ds1
spring.shardingsphere.sharding.tables.orders.actual-data-nodes=ds$->{0..1}.orders_$->{0..9}
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.algorithm-expression=orders_$->{user_id % 10}
效果:单表数据量从 5000 万降低到 500 万,查询性能提升 5 倍。
最终效果
📊 性能提升对比
- 响应时间:2s → 300ms(提升 85%)
- CPU 使用率:85% → 40%(降低 53%)
- Full GC 频率:3-5 次/分钟 → 1 次/小时(降低 95%)
- 数据库连接数:200 → 50(降低 75%)
- 系统吞吐量:1000 QPS → 3000 QPS(提升 200%)
总结:性能优化的核心思路
- 监控先行 - 没有数据就没有优化,建立完善的监控体系
- 找到瓶颈 - 用工具定位真正的性能瓶颈,不要盲目优化
- 分层优化 - JVM → 代码 → 数据库 → 架构,从下往上优化
- 渐进式改进 - 每次优化后验证效果,避免过度优化
- 权衡取舍 - 性能 vs 可维护性,找到平衡点
性能优化不是一蹴而就的,需要持续监控和改进。希望这篇实战经验对你有所帮助!