背景:性能瓶颈的发现

我们的电商系统在用户量达到百万级别后,响应时间从 200ms 飙升到 2s。通过性能监控工具(Prometheus + Grafana)发现,主要瓶颈在:

第一步: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%

总结:性能优化的核心思路

  1. 监控先行 - 没有数据就没有优化,建立完善的监控体系
  2. 找到瓶颈 - 用工具定位真正的性能瓶颈,不要盲目优化
  3. 分层优化 - JVM → 代码 → 数据库 → 架构,从下往上优化
  4. 渐进式改进 - 每次优化后验证效果,避免过度优化
  5. 权衡取舍 - 性能 vs 可维护性,找到平衡点

性能优化不是一蹴而就的,需要持续监控和改进。希望这篇实战经验对你有所帮助!