Spring Boot异步接口性能优化:从单线程到高并发的优化历程
Spring Boot异步接口性能优化:从单线程到高并发的优化历程
在现代Web应用开发中,接口性能往往是系统瓶颈的关键所在。特别是当业务逻辑涉及多个外部服务调用时,传统的同步处理方式会导致线程阻塞,严重影响系统吞吐量,因此进行优化。
业务场景描述
用户信息聚合接口需要从三个不同的数据源获取信息:
- 基础信息查询:耗时100ms(数据库查询)
- 详情信息查询:耗时300ms(复杂业务查询)
- 扩展信息查询:耗时500ms(第三方API调用)
这是一个典型的IO密集型场景,同步执行总耗时900ms,而理论上并行执行只需要600ms(基础信息查询必须最先执行后面并行)。
同步版本
@GetMapping("/user/sync")
public User getUserSync() throws InterruptedException {return userService.assembleUser();
}public User assembleUser() throws InterruptedException {long startTime = System.currentTimeMillis();// 1. 获取基础信息(100ms)Long userId = getUserBaseInfo();// 2. 获取详细信息(300ms)UserDetail userDetail = getUserDetailInfo(userId);// 3. 获取扩展信息(500ms)UserExtraInfo extraInfo = getUserExtraInfo(userId);// 组装完整User对象User user = new User();user.setId(userId);user.setName(userDetail.getName());user.setAge(userDetail.getAge());user.setSex(userDetail.getSex());user.setPhone(extraInfo.getPhone());user.setLevel(extraInfo.getLevel());System.out.println("用户对象组装完成,总耗时: " + (System.currentTimeMillis() - startTime) + "ms");return user;
}
测试结果:
- 单次调用耗时:~900ms
- 同步结果: User(id=1001, name=张三, age=25, sex=1, level=VIP会员, phone=13800138000)
异步并行
意识到这三个查询之间存在依赖关系(详情和扩展信息都需要userId),我设计了一个并行执行方案:
@GetMapping("/user/async-ultra")
public User getUserAsyncUltra() throws Exception {return userService.assembleUserUltraPerformance();
}public User assembleUserUltraPerformance() throws Exception {long startTime = System.currentTimeMillis();// 先获取基础信息CompletableFuture<Long> baseInfoFuture = CompletableFuture.supplyAsync(() -> {try {return getUserBaseInfo();} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}}, highPerformanceExecutor);// 基于baseInfo并行获取详情和扩展信息CompletableFuture<UserDetail> detailFuture = baseInfoFuture.thenApplyAsync(userId -> {try {return getUserDetailInfo(userId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}}, highPerformanceExecutor);CompletableFuture<UserExtraInfo> extraFuture = baseInfoFuture.thenApplyAsync(userId -> {try {return getUserExtraInfo(userId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}}, highPerformanceExecutor);// 等待所有任务完成CompletableFuture<Void> allTasks = CompletableFuture.allOf(baseInfoFuture, detailFuture, extraFuture);allTasks.get();// 获取结果并组装(此时所有任务都已完成,不会阻塞)Long userId = baseInfoFuture.get();UserDetail detail = detailFuture.get();UserExtraInfo extra = extraFuture.get();User user = new User();user.setId(userId);user.setName(detail.getName());user.setAge(detail.getAge());user.setSex(detail.getSex());user.setPhone(extra.getPhone());user.setLevel(extra.getLevel());System.out.println("极致性能组装完成,总耗时: " + (System.currentTimeMillis() - startTime) + "ms");return user;
}
测试结果:
- 单次调用耗时:~600ms(相比同步版本提升33%)
- 异步结果: User(id=1001, name=张三, age=25, sex=1, level=VIP会员, phone=13800138000)
对比结果如下图:

问题发现:高并发下的性能反转
虽然单次调用异步版本更快,但在JMeter压测中却发现了一个令人困惑的现象:在高并发场景下,异步接口的吞吐量竟然没有明显提升!
压测环境
- 硬件配置:8核CPU,16GB内存
- JMeter配置:150并发线程,60秒Ramp-up,180秒持续时间

压测结果如下图:
同步压测结果如图:

异步压测结果如图:

而且最严重的是:异步接口耗时时间显著增加
开始日志打印: 极致性能组装完成,总耗时: 601ms-711ms
后期日志打印: 极致性能组装完成,总耗时: 22441ms-22632ms
个人分析:
- 150并发请求同时到达
- 每个请求需要3个线程(baseInfo + detail + extra)
- 理论需要: 150 × 3 =450个线程 实际只有: 最大12个线程
- 线程严重不足!
线程池
初始的线程池配置:
@Bean("highPerformanceExecutor")public ThreadPoolTaskExecutor highPerformanceExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 更保守的配置,减少线程切换executor.setCorePoolSize(6); // 略小于CPU核数executor.setMaxPoolSize(12); // CPU核数 * 1.5executor.setQueueCapacity(500);executor.setKeepAliveSeconds(30);executor.setThreadNamePrefix("HighPerf-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
在150并发下,这个配置导致了严重的线程竞争。
线程池参数调优
@Bean("highPerformanceExecutor")public ThreadPoolTaskExecutor highPerformanceExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 针对高并发调整executor.setCorePoolSize(50); // 增加核心线程executor.setMaxPoolSize(150); // 增加最大线程executor.setQueueCapacity(50); // 减小队列,快速扩展线程executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("HighPerf-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
压测结果如下:可以看到吞吐量得到了明显提升

数据对比:
同步接口: 137 TPS
异步接口(优化前): 6.6 TPS
异步接口(优化后): 142 TPS
关键发现:
异步接口TPS提升了 2050% (142/6.6)
相比同步接口提升了 3.6% (142/137)
继续增大线程数压测如下图: 吞吐量提升到了175
@Bean("highPerformanceExecutor")public ThreadPoolTaskExecutor highPerformanceExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 针对高并发调整executor.setCorePoolSize(100); // 增加核心线程executor.setMaxPoolSize(300); // 增加最大线程executor.setQueueCapacity(50); // 减小队列,快速扩展线程executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("HighPerf-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}

分析:
不是设置得越高越好!虽然提升了吞吐量,但隐藏着很大的风险。这是一个典型的"用资源换性能"的策略,需要谨慎使用,而且后期的接口响应时间明显增加
1.🎯
每个线程栈大小:1MB (默认)
300个线程:300MB 栈内存
加上堆内存、方法区等:总内存需求激增
- OutOfMemoryError
- 频繁Full GC导致STW(Stop The World)
- 系统响应变慢
2.📊
理想情况:8个线程同时运行
300个线程:需要频繁的上下文切换
切换成本:每次切换消耗几微秒到几毫秒
- 轻微的压力就会创建大量线程,但线程数远远超过CPU处理能力
- CPU利用率虚高但实际处理能力下降
- 系统整体性能下降
3⚡
高并发(500+)下:严重不足
实际需求:500 × 3 = 1500个线程
结果:大量任务被CallerRunsPolicy拒绝,回到主线程执行
使用JVisualVM分析
- 线程数量激增:异步版本创建了大量线程
- CPU上下文切换频繁:8核CPU的上下文切换成为瓶颈
- 内存使用增加:每个线程占用约1MB栈空间
同步分析:

异步分析:

jvm参数增加堆的内存大小:-Xmx2g -Xms2g ,继续测试如下:

再次压测异步:

吞吐量前后差别不大,通过内存分析工具分析,之前的内存已经够用,GC发生次数也不多,内存增加反而可能带来负面影响:
- GC暂停时间变长:虽然频率降低,但每次Full GC时间可能从100ms→2s
- 内存扫描成本:G1GC需要扫描整个堆,堆越大扫描时间越长
线程切换次数优化
优化代码:减少一次线程切换
public User assembleUserOptimized() throws Exception {long startTime = System.currentTimeMillis();// 在当前线程执行最快的任务Long userId = getUserBaseInfo(); // 100ms,无需异步// 只对耗时任务异步CompletableFuture<UserDetail> detailFuture = CompletableFuture.supplyAsync(() -> {try {return getUserDetailInfo(userId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}}, highPerformanceExecutor);CompletableFuture<UserExtraInfo> extraFuture = CompletableFuture.supplyAsync(() -> {try {return getUserExtraInfo(userId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}}, highPerformanceExecutor);// 并行等待UserDetail detail = detailFuture.get();UserExtraInfo extra = extraFuture.get();User user = new User();user.setId(userId);user.setName(detail.getName());user.setAge(detail.getAge());user.setSex(detail.getSex());user.setPhone(extra.getPhone());user.setLevel(extra.getLevel());System.out.println("高性能并行组装完成,总耗时: " + (System.currentTimeMillis() - startTime) + "ms");return user;}
压测结果如下:确实明显提升了吞吐量

优化Tomcat配置(提升点)
server:tomcat:threads:max: 300 # 增加最大线程数min-spare: 50 # 增加最小线程数accept-count: 200 # 增加连接队列max-connections: 2000 # 增加最大连接数
动态线程池补充
在Java开发中,ThreadPoolExecutor的使用常常面临以下挑战:
- 参数配置难:核心参数凭经验设置,无法精准匹配业务场景
- 调整成本高:参数优化需修改代码重启服务,影响业务连续性
- 运行无感知:线程池负载状态缺乏监控,问题爆发后才能察觉
动态线程池参考:springboot整合动态线程池
虚拟线程
虚拟线程是 Java 中的一种轻量级线程,它旨在解决传统线程模型中的一些限制,提供了更高效的并发处理能力,允许创建数千甚至数万个虚拟线程,而无需占用大量操作系统资源。
虚拟线程与传统线程具有如下几个优势:
- 更加轻量级:虚拟线程相比传统线程更加轻量级。因为它们不是直接映射到操作系统线程上,而是在用户空间内被管理。这种设计减少了线程创建和销毁的开销,允许在同一应用中运行成千上万的线程。
- 资源消耗更少:由于不是直接映射到操作系统线程,虚拟线程显著降低了内存和其他资源的消耗。这使得在有限资源下可以创建更多的线程。
- 上下文切换开销更低:由于虚拟线程在用户空间,而不是通过操作系统,所以它的上下文切换开销更低。
- 改善阻塞操作的处理:在传统线程模型中,阻塞操作(如 I/O)会导致整个线程被阻塞,浪费宝贵的系统资源。然而当一个虚拟线程阻塞时,它可以被挂起,底层的操作系统线程则可以用来运行其他虚拟线程。
- 简化并发编程:可以像编写普通顺序代码一样编写并发代码,而不需要过多考虑线程管理和调度。它简化了 Java 的并发编程模型。
- 提升性能:在 I/O 密集型应用中,虚拟线程能够显著地提升性能。而且由于它们的创建和销毁成本低,能够更加高效地利用系统资源。
参考学习: 虚拟线程
更改代码如下:
public User assembleUserVirtualThread() throws Exception {long startTime = System.currentTimeMillis();// 在当前线程执行最快的任务Long userId = getUserBaseInfo(); // 100ms,无需异步// 使用虚拟线程执行器try (ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor()) {// 只对耗时任务使用虚拟线程CompletableFuture<UserDetail> detailFuture = CompletableFuture.supplyAsync(() -> {try {return getUserDetailInfo(userId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}}, virtualExecutor);CompletableFuture<UserExtraInfo> extraFuture = CompletableFuture.supplyAsync(() -> {try {return getUserExtraInfo(userId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}}, virtualExecutor);// 并行等待UserDetail detail = detailFuture.get();UserExtraInfo extra = extraFuture.get();User user = new User();user.setId(userId);user.setName(detail.getName());user.setAge(detail.getAge());user.setSex(detail.getSex());user.setPhone(extra.getPhone());user.setLevel(extra.getLevel());System.out.println("虚拟线程组装完成,总耗时: " + (System.currentTimeMillis() - startTime) + "ms");return user;}}
压测结果如下:吞吐量再次得到提升

最最最重要的是:上下文切换开销更低:由于虚拟线程在用户空间,而不是通过操作系统,所以它的上下文切换开销更低。
直到压测结束: 接口的耗时时间均为600ms左右,相比之前的传统线程方案,后期的接口响应时间没有增加
虚拟线程组装完成,总耗时: 601ms
虚拟线程组装完成,总耗时: 601ms
虚拟线程组装完成,总耗时: 604ms
虚拟线程组装完成,总耗时: 602ms
虚拟线程组装完成,总耗时: 604ms
虚拟线程组装完成,总耗时: 602ms
虚拟线程组装完成,总耗时: 602ms
虚拟线程组装完成,总耗时: 602ms
虚拟线程组装完成,总耗时: 603ms
虚拟线程组装完成,总耗时: 601ms
虚拟线程组装完成,总耗时: 601ms
Spring WebFlux响应式编程
响应式编程(Reactive Programming)作为一种新的编程范式,解决了传统编程中存在的一些问题,尤其是在高并发和高延迟的场景下。Spring WebFlux 作为 Spring 5 引入的一部分,为开发者提供了一种基于响应式流的编程模型,结合 Reactor 库,能够高效处理异步非阻塞的请求。
参考学习: WebFlux
更改代码如下:
@GetMapping("/user/pure-reactive")public Mono<User> getUserPureReactive() {return userService.assembleUserWebFluxVirtualOptimized();}@Bean("virtualThreadScheduler")public Scheduler virtualThreadScheduler() {return Schedulers.fromExecutor(Executors.newVirtualThreadPerTaskExecutor());}@Autowired@Qualifier("virtualThreadScheduler")private Scheduler virtualThreadScheduler; // 注入全局调度器public Mono<User> assembleUserWebFluxVirtualOptimized() {long startTime = System.currentTimeMillis();return Mono.fromCallable(() -> getUserBaseInfo()).subscribeOn(virtualThreadScheduler) // 使用注入的调度器.flatMap(userId -> {Mono<UserDetail> detailMono = Mono.fromCallable(() -> getUserDetailInfo(userId)).subscribeOn(virtualThreadScheduler);Mono<UserExtraInfo> extraMono = Mono.fromCallable(() -> getUserExtraInfo(userId)).subscribeOn(virtualThreadScheduler);return Mono.zip(detailMono, extraMono).map(tuple -> {UserDetail detail = tuple.getT1();UserExtraInfo extra = tuple.getT2();User user = new User();user.setId(userId);user.setName(detail.getName());user.setAge(detail.getAge());user.setSex(detail.getSex());user.setPhone(extra.getPhone());user.setLevel(extra.getLevel());System.out.println("优化WebFlux+虚拟线程组装完成,总耗时: " +(System.currentTimeMillis() - startTime) + "ms");return user;});});}
压测结果如下: 整体相差不大,可能还需要结合其余的场景进行分析

另外:虚拟线程和WebFlux确实是两种完全不同的并发范式,通常不推荐混合使用

纯webFlux版本:
代码更改:
@Bean("customScheduler")public Scheduler customScheduler() {Scheduler customScheduler = Schedulers.fromExecutor(Executors.newFixedThreadPool(200, r -> {Thread t = new Thread(r, "webflux-custom-" + System.currentTimeMillis());t.setDaemon(true);return t;}));return customScheduler;}@Autowired@Qualifier("customScheduler")private Scheduler customScheduler;public Mono<User> assembleUserCustomSchedulerWebFlux() {long startTime = System.currentTimeMillis();// 使用自定义的高性能调度器return Mono.fromCallable(() -> getUserBaseInfo()).subscribeOn(customScheduler).flatMap(userId -> {Mono<UserDetail> detailMono = Mono.fromCallable(() -> getUserDetailInfo(userId)).subscribeOn(customScheduler);Mono<UserExtraInfo> extraMono = Mono.fromCallable(() -> getUserExtraInfo(userId)).subscribeOn(customScheduler);return Mono.zip(detailMono, extraMono).map(tuple -> {UserDetail detail = tuple.getT1();UserExtraInfo extra = tuple.getT2();User user = new User();user.setId(userId);user.setName(detail.getName());user.setAge(detail.getAge());user.setSex(detail.getSex());user.setPhone(extra.getPhone());user.setLevel(extra.getLevel());System.out.println("自定义调度器WebFlux组装完成,总耗时: " +(System.currentTimeMillis() - startTime) + "ms");return user;});});}
吞吐量测试如下:

注意:
如果使用了webFlux后启动的一直是Tomcat,可以加入下面代码:
@Beanpublic org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {return new org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory(9000);}
缓存优化
JetCache 参考学习: SpringBoot3整合JetCache缓存
在同步的代码上加上缓存注解:
@GetMapping("/user/async-ultra")@Cached(name="userCache:", expire = 3600, localExpire = 3500, cacheType = CacheType.BOTH)public User getUserAsyncUltra() throws Exception {return userService.assembleUserVirtualThread();}
压测结果如下: 恐怖如斯!!!!

