如何提升单体 Spring Boot 应用的并发数?

1. 引言

单体 Spring Boot 应用在业务初期往往是最务实的选择:部署简单、调试方便、团队上手快。然而随着用户量增长,并发请求一上来,应用响应变慢、CPU 飙升、数据库连接池被打满,各种问题接踵而至。很多人第一反应是「拆微服务」,但拆服务成本高、周期长,未必是当下最优解。

其实在单体架构内,通过合理的配置调优、线程池治理、连接池优化、缓存引入和代码层面的并发改造,往往就能把并发能力提升数倍甚至一个数量级。本文从配置层、容器层、数据层、代码层四个维度,系统梳理单体 Spring Boot 提升并发数的实战手段。

2. 先搞清楚瓶颈在哪

提升并发数之前,先要回答一个问题:当前系统的瓶颈到底在哪? 盲目调参往往事倍功半。

常见的瓶颈点包括:

  • CPU 密集:大量计算、加解密、序列化,CPU 使用率长期 90% 以上。
  • IO 密集:频繁访问数据库、Redis、外部 HTTP 接口,线程大量阻塞在等待 IO。
  • 内存受限:GC 频繁、堆内存不足,导致 Full GC 拖垮吞吐。
  • 连接池耗尽:数据库连接池、HTTP 连接池被打满,请求排队超时。
  • 锁竞争:同步块、分布式锁、数据库行锁竞争激烈,线程大量阻塞。

定位手段:

  • 用 top、vmstat 观察 CPU、上下文切换;
  • 用 jstack 抓线程栈,看线程到底阻塞在哪;
  • 用 Arthas 的 dashboard、thread 命令快速定位热点线程;
  • 用 APM 工具(SkyWalking、Prometheus + Grafana)观察接口耗时分布。

原则:先度量,再优化。没有数据支撑的调优都是碰运气。

3. 配置层:JVM 与 Spring Boot 基础调优

3.1 JVM 参数

合理的堆内存设置能显著减少 GC 停顿。示例:

bash 复制代码
java -Xms4g -Xmx4g -Xmn2g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=100 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -jar app.jar

要点:

  • -Xms 与 -Xmx 设为相同值,避免运行时动态扩容带来的性能损耗;
  • 根据业务对象生命周期调整新生代大小;
  • 开启 GC 日志,便于事后分析。

3.2 Spring Boot 内嵌 Tomcat 调优

Spring Boot 默认使用内嵌 Tomcat,核心配置如下:

yaml 复制代码
server:
  tomcat:
    threads:
      max: 200          # 最大工作线程数
      min-spare: 20     # 最小空闲线程数
    accept-count: 200   # 等待队列长度
    max-connections: 10000  # 最大连接数
    connection-timeout: 5000

注意:线程数并非越大越好。线程过多会导致上下文切换开销剧增,反而降低吞吐。一般经验值:

  • CPU 密集型:线程数 ≈ CPU 核数 + 1
  • IO 密集型:线程数 ≈ CPU 核数 × 2(配合虚拟线程可进一步放宽)

3.3 启用虚拟线程(JDK 21+)

如果项目已升级到 JDK 21,可以开启虚拟线程,极大提升 IO 密集型场景的并发能力:

yaml 复制代码
spring:
  threads:
    virtual:
      enabled: true

开启后,每个请求不再独占一个平台线程,而是由虚拟线程承载,线程数上限大幅提升,非常适合大量阻塞在数据库/Redis/远程调用的场景。

4. 容器层:连接池与线程池治理

4.1 数据库连接池

数据库连接池是单体应用最常见的瓶颈。以 HikariCP(Spring Boot 默认)为例:

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

连接池大小经验公式:

复制代码
连接数 = ((核心线程数 × 2) + 有效磁盘数)

但更务实的做法是:压测得出最优值。连接池过小会排队,过大则数据库本身成为瓶颈。

4.2 HTTP 客户端连接池

如果单体应用需要调用外部服务,务必使用带连接池的 HTTP 客户端(如 RestTemplate + HttpClient、WebClient + Reactor Netty),避免每次请求都新建连接。以 Apache HttpClient 为例:

java 复制代码
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(50);

4.3 业务线程池隔离

把不同业务的线程池隔离,避免某个慢接口拖垮整个应用。例如用 ThreadPoolTaskExecutor 单独处理异步任务:

java 复制代码
@Bean("asyncExecutor")
public ThreadPoolTaskExecutor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(50);
    executor.setQueueCapacity(200);
    executor.setThreadNamePrefix("async-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}

5. 数据层:缓存与查询优化

5.1 引入本地缓存 + Redis 多级缓存

缓存是提升并发最立竿见影的手段。热点数据优先走本地缓存(Caffeine),未命中再查 Redis,最后才落到数据库:

java 复制代码
@Cacheable(cacheNames = "user", key = "#id")
public User getUserById(Long id) {
    return userMapper.selectById(id);
}

5.2 数据库查询优化

  • 为高频查询字段建立合适的索引;
  • 避免 SELECT *,只查必要字段;
  • 分页查询用覆盖索引或游标分页,避免深分页;
  • 复杂统计查询考虑异步落库 + 定时汇总。

5.3 读写分离

如果读多写少,可以配置主从数据源,读请求走从库,写请求走主库,显著降低主库压力。

6. 代码层:并发改造与性能热点优化

6.1 接口幂等与并发安全

高并发下要特别注意共享资源的线程安全:

  • SimpleDateFormat 非线程安全,改用 DateTimeFormatter 或 ThreadLocal;
  • HashMap 并发写会丢数据,改用 ConcurrentHashMap;
  • 数据库扣减库存用乐观锁或 UPDATE ... WHERE stock > 0 原子操作。

6.2 串行改并行

多个无依赖的远程调用或查询,可以用 CompletableFuture 并行执行,缩短接口响应时间:

java 复制代码
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userService.get(id));
CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> orderService.get(id));

User user = userFuture.get();
Order order = orderFuture.get();

6.3 热点代码优化

  • 减少不必要的对象创建,避免频繁 GC;
  • 用 StringBuilder 替代字符串拼接;
  • 避免在循环中做数据库查询(N+1 问题);
  • 大对象、大集合操作尽量异步化或分片处理。

7. 压测验证:让数据说话

调优完成后,必须用压测验证效果。推荐工具:

  • JMeter:简单易用,适合接口级压测;
  • wrk:命令行工具,适合快速压测;
  • Arthas:线上问题定位,观察线程、GC、方法耗时。

压测要点:

  • 逐步增加并发数,观察吞吐量(TPS)和响应时间(RT)的变化曲线;
  • 关注 P99 延迟,而不是只看平均值;
  • 压测环境尽量贴近生产(数据量、硬件配置)。

8. 总结

单体 Spring Boot 提升并发数,核心思路是先定位瓶颈,再分层优化:

层次 关键手段
配置层 JVM 参数、Tomcat 线程数、虚拟线程
容器层 数据库/HTTP 连接池、业务线程池隔离
数据层 多级缓存、索引优化、读写分离
代码层 并发安全、并行化改造、热点优化

单体架构的并发天花板并不低,关键在于系统性地排查瓶颈、有针对性地调优。当单体经过充分优化仍无法满足业务需求时,再考虑拆分微服务也不迟。

相关推荐
阡陌数智36 分钟前
Claude API新版本重大变更与安全升级指南
java·linux·安全
学长毕设2 小时前
基于SpringBoot的野生动物公益保护平台设计与实现-附源码
java·spring boot·后端
梦里寻码3 小时前
我用 NestJS + Vue3 写了一套完整的研发管理系统(58 表 / 229 API / 130 项断言,全开源
前端·后端·算法
鶴哥只手遮天3 小时前
《Redis 8.10.3 深度剖析》系列 ·第 10 章 Hash 一:`listpack`、`hashtable` 与 `LISTPACK_EX`
redis·后端·开源
SL_staff3 小时前
离散制造排程破局:JVS-APS 四步闭环落地实践(开发者视角)
java·产品·全栈
johnjincaaaa3 小时前
WebAssembly 逆向实战:从 0 搞懂猿人学第 15 题
后端
Gopher_HBo3 小时前
Go CPU 剖析:四角色白话机制 + 实测实验
后端
这个昵称不容易3 小时前
SpringBoot 美容院管理系统:232个Java文件315个接口,5套角色从预约到发货一整条链路
java·springboot·毕设
猿人谷3 小时前
Surrogate Model:让进化算法学会“预测”,为什么它正在成为智能优化的重要技术?
后端·算法·架构
她的男孩3 小时前
三人会签只批了一个就往下走:Forge Admin 把 all / any / ratio 写成 completionCondition
spring boot·低代码·工作流引擎