首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >SpringBoot 项目越写越慢?性能调优的那些坑和血泪史

SpringBoot 项目越写越慢?性能调优的那些坑和血泪史

原创
作者头像
用魔法才能打败魔法
发布2025-11-17 16:40:29
发布2025-11-17 16:40:29
130
举报

前言

之前我们团队有个项目,上线初期 RT(响应时间)是 30ms 左右,QPS 能到 1000。但随着业务功能越来越多,RT 一路飙到 120ms,甚至有几次直接超时。当时大家以为是数据库出了问题,结果排查下来,发现其实很多问题都藏在了代码里。

说白了,SpringBoot 项目“越写越慢”不是因为框架本身有问题,而是我们在开发过程中忽略了一些细节。比如线程池配置、缓存使用、数据库查询方式、GC 策略等等。这些问题慢慢积累,最终导致系统整体性能下降。

数据库连接池配置不当,真的会拖垮系统?

记得有一次线上故障,系统 CPU 负载飙到 90%+,而数据库连接池几乎被打满。那时候我们用的是默认的 HikariCP 配置,没有设置 maxPoolSize,结果大量请求堆积在连接池中,导致整个服务卡顿。

错误做法:默认配置不动

代码语言:yaml
复制
spring:
  datasource:
    hikari:
      maxPoolSize: 10   # 默认值,非常容易被打满

正确做法:依据 QPS 设置连接池大小

代码语言:yaml
复制
spring:
  datasource:
    hikari:
      minimum-idle: 10
      maximum-pool-size: 50
      idle-timeout: 30000
      max-lifetime: 1800000
      connection-timeout: 2000

Controller 层的 DTO 转换,真的没那么轻量?

有一次我优化一个接口,发现每次请求都要做大量的 DTO 转换,从 Request 到 VO 再到 Entity,中间用了多个工具类。虽然看起来没什么问题,但实际测试的时候,发现这个转换过程占了整个请求的 20% 左右。

错误做法:频繁使用 BeanUtils

代码语言:java
复制
UserVO vo = new UserVO();
BeanUtils.copyProperties(entity, vo);

正确做法:使用 MapStruct,速度快 10–15 倍

代码语言:java
复制
@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    UserVO toVO(UserEntity entity);
}

调用:

代码语言:java
复制
UserVO vo = UserMapper.INSTANCE.toVO(entity);

Service 层太臃肿,怎么拆?

之前有一个订单服务,里面逻辑特别复杂,各种 if-else,还有嵌套循环。每次处理一个订单,都要走几十步操作,结果导致接口 RT 直接翻倍。

错误做法:超大方法

代码语言:java
复制
public void processOrder(Order order) {
    // 几百行代码...
}

正确做法:职责拆分 + 策略模式

代码语言:java
复制
public interface PayStrategy {
    void pay(Order order);
}
代码语言:java
复制
@Service("wechatPay")
public class WechatPayStrategy implements PayStrategy {
    public void pay(Order order) { ... }
}

优雅调用:

代码语言:java
复制
payStrategyMap.get(order.getPayType()).pay(order);

MyBatis 查询太慢,到底是哪里出问题了?

有一次我们发现一个查询接口 RT 超过 100ms,但 SQL 执行却只有 5ms。后来发现是 MyBatis 没有正确使用缓存,导致每次请求都重新查数据库。

开启 MyBatis 二级缓存:

代码语言:xml
复制
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

Mapper 中:

代码语言:xml
复制
<cache eviction="LRU" flushInterval="60000" size="512"/>

另一个典型坑:N+1 查询问题

错误写法:

代码语言:java
复制
List<User> users = userMapper.list();
for (User u : users) {
    u.setOrders(orderMapper.getByUserId(u.getId()));
}

正确写法:一次 JOIN 查完。

代码语言:sql
复制
SELECT u.*, o.*
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 1;

Redis 缓存命中率低怎么办?

有一次我们发现 Redis 的命中率不到 60%,后来发现是 key 的 TTL 过短、高频数据没预热等导致的。

检查慢查询:

代码语言:bash
复制
redis-cli SLOWLOG GET 100

检查 BigKey:

代码语言:bash
复制
redis-cli MEMORY USAGE user:info:123

缓存预热示例:

代码语言:java
复制
@PostConstruct
public void warmUp() {
    List<User> hotUsers = userMapper.listHot();
    hotUsers.forEach(u -> redisTemplate.opsForValue()
        .set("user:" + u.getId(), u, Duration.ofHours(12)));
}

JVM 参数和 GC 策略,到底应该怎么调?

我们有个服务 Full GC 每分钟一次,导致系统抖动严重。

后来调整 JVM:

代码语言:bash
复制
-Xms4g
-Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-Xlog:gc*:file=./logs/gc.log:time

设置 Xms = Xmx 能避免堆动态扩容带来的 STW。

怎么建立一套可持续的性能监控体系?

我们团队后来做了个简单的性能监控方案,包括以下几个部分:

  • 使用 Actuator 提供的 /actuator/metrics 接口获取系统指标;
  • 结合 Prometheus + Grafana 做可视化监控;
  • 对关键接口做埋点,记录 RT、成功率等;
  • 定期生成性能报告,找出潜在问题。

有了这套体系后,我们能更快地发现性能异常,而不是等到线上出问题才去排查。

一次真实的性能优化案例:从 120ms 到 30ms

有一次我们优化了一个用户查询接口,原本 RT 是 120ms,经过一系列调整后,最终稳定在 30ms 以内。

具体做了以下几件事:

  • 优化了数据库查询语句,减少 N+1 查询;
  • 引入 Redis 缓存高频数据;
  • 合理配置线程池和数据库连接池;
  • 减少 DTO 转换次数;
  • 调整 JVM 参数,降低 GC 频率。

整个过程花了大概一周时间,但效果非常明显,用户反馈也好了很多。

几点建议

  • 不要一开始就追求高并发,先保证基础架构的稳定性;
  • 多关注日志和监控,别等到出问题才去查;
  • 学会用工具定位问题,而不是靠直觉;
  • 尽量避免重复造轮子,多用成熟的工具和框架;
  • 持续学习,保持对性能的敏感度。

最后碎碎念

说实话,性能调优不是一蹴而就的事情,它需要你不断观察、分析、实验。很多时候你以为自己已经做得很好了,但实际运行中还是会有隐藏的问题。别怕犯错,别怕调试,多写点日志,多看点 GC 日志,多用点工具。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 数据库连接池配置不当,真的会拖垮系统?
  • Controller 层的 DTO 转换,真的没那么轻量?
  • Service 层太臃肿,怎么拆?
  • MyBatis 查询太慢,到底是哪里出问题了?
  • Redis 缓存命中率低怎么办?
  • JVM 参数和 GC 策略,到底应该怎么调?
  • 怎么建立一套可持续的性能监控体系?
  • 一次真实的性能优化案例:从 120ms 到 30ms
  • 几点建议
  • 最后碎碎念
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档