应用能跑起来,只是第一步。跑得快、跑得稳,才是生产环境的要求。
很多 SpringBoot 项目在上线初期一切正常,随着数据量增长、用户量增加,慢慢就变慢了。这时候再去优化,往往要花好几倍的时间。
性能优化最好的时机,是写代码的时候。
这篇文章从实际项目出发,总结了一套 SpringBoot 性能优化的"套路",按照见效快慢排序,先搞最容易出效果的。
一、优化优先级
性能优化不是瞎搞,要有优先级:
| 优化方向 | 预期效果 |
|---|---|
| 数据库(索引、SQL、连接池) | 提升最大,最常见瓶颈 |
| 缓存(Redis、本地缓存) | 减少重复计算 |
| 接口逻辑 | 减少不必要操作 |
| 容器线程池 | 提升并发能力 |
| JVM 参数 | 减少 GC 停顿 |
记住:80% 的性能问题出在数据库,别一上来就去调 JVM。
二、数据库优化
数据库是绝大多数性能问题的根源,也是优化收益最大的地方。
2.1 先找到慢 SQL
不知道慢在哪里就没法优化。SpringBoot 提供了几种方式定位慢 SQL:
-
开启 SQL 日志打印,观察执行时间
-
使用 Druid 连接池的内置监控页面,可视化查看慢 SQL 排行
-
在数据库层面开启慢查询日志
找到那些执行慢、调用频繁的 SQL,就是优化的突破口。
2.2 索引是重中之重
索引就像书的目录,没有索引就是从头翻到尾。
需要加索引的场景包括:WHERE 条件中的字段、ORDER BY 排序字段、JOIN 关联字段。但也需要注意,索引不是越多越好,因为索引会占用空间,还会降低写入速度。
常见的索引失效情况有:在索引列上使用函数、使用不等于运算、LIKE 以通配符开头等。
2.3 避免 N+1 查询
这是非常常见的问题:先查出一批数据,再循环逐条查询关联数据。
解决方案有两种:一是使用 JOIN 一次性查出所有关联数据,二是使用批量查询,一次性查出所有需要的关联数据,然后在内存中组装。
2.4 连接池配置要合理
SpringBoot 默认使用 HikariCP,这是目前性能最好的连接池。
连接数不是越大越好。每个连接都会占用内存和 CPU,连接过多反而会导致上下文切换频繁,性能下降。通常 10 到 20 个连接就足够了,具体可以根据压测结果调整。
三、缓存优化
缓存的核心思想:把经常读、不常变的数据存起来,下次直接从缓存拿。
3.1 本地缓存
本地缓存适合数据量小、不跨服务共享的场景,比如字典表、配置信息。
SpringBoot 内置了缓存抽象,只需要在方法上添加 @Cacheable 注解,返回值就会被自动缓存。配合 Caffeine 这种高性能本地缓存库,可以设置过期时间、最大数量等。
3.2 分布式缓存
Redis 是分布式缓存的首选,适合跨服务共享的数据,比如用户会话、热点业务数据。
使用缓存时需要注意几个经典问题:
-
缓存穿透:查询不存在的数据,解决方案是缓存空值或使用布隆过滤器
-
缓存雪崩:大量缓存同时失效,解决方案是给过期时间加上随机值
-
缓存击穿:热点 Key 失效瞬间大量请求打到数据库,解决方案是使用互斥锁或设置热点数据永不过期
四、接口优化
4.1 按需返回字段
很多时候前端只需要几个字段,但接口把整个实体对象都返回了。按需返回可以减少网络传输量,也能降低数据库查询压力。
4.2 分页查询
列表接口一定要分页,不要一次性查询所有数据。数据量大了之后,一次性查询不仅慢,还可能导致内存溢出。
4.3 非核心逻辑异步化
发送邮件、记录操作日志、推送消息等非核心逻辑,可以放到异步线程池中执行,不让它们阻塞主流程。SpringBoot 的 @Async 注解可以轻松实现。
4.4 避免大事务
事务中不要包含远程调用、大量循环操作等耗时逻辑。事务持有锁的时间越长,并发越差。应该只把真正需要保证一致性的操作放在事务中。
五、容器与线程池优化
5.1 Tomcat 线程池
SpringBoot 内嵌的 Tomcat 默认线程数不算大,可以根据服务器配置适当调整。最大工作线程数建议设置在 200 左右,太小了并发能力不足,太大了反而增加上下文切换开销。
5.2 业务线程池
业务中的异步任务应该有独立的线程池,避免占用 Tomcat 的工作线程。线程池的核心参数需要根据任务类型来定:IO 密集型任务可以设置大一点的线程数,CPU 密集型任务线程数不宜超过 CPU 核心数。
六、JVM 参数优化
JVM 参数优化排在最后,因为大多数应用还不到这一步。
最基础的配置是设置堆内存大小:-Xms 和 -Xmx 设置为相同的值,避免运行中扩容。垃圾回收器推荐使用 G1,设置合理的停顿目标即可。
对于容器化部署,堆内存需要根据容器内存限制来设置,通常设为容器内存的 70% 左右,留一些给系统和其它进程。
七、排查工具推荐
当应用变慢了,需要工具来定位问题:
Arthas 是阿里开源的 Java 诊断工具,可以在线看线程状态、方法调用耗时、入参出参,非常强大。
压测工具 方面,ab(Apache Bench)适合简单快速的压测,JMeter 功能更全面,支持复杂的测试场景。
总结
性能优化是一套方法论,不是玄学:
| 阶段 | 做什么 |
|---|---|
| 写代码时 | 注意索引、分页、缓存、异步 |
| 测试时 | 压测、定位瓶颈 |
| 上线前 | 检查清单过一遍 |
| 出问题时 | 用工具定位,针对性优化 |
最后记住一句话:
过早优化是万恶之源,但不优化是更大的恶。优先搞定数据库和缓存,其他地方按需优化就好。