当前位置: 首页 > news >正文

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分析

  1. 线程数量激增:异步版本创建了大量线程
  2. CPU上下文切换频繁:8核CPU的上下文切换成为瓶颈
  3. 内存使用增加:每个线程占用约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的使用常常面临以下挑战:

  1. 参数配置难:核心参数凭经验设置,无法精准匹配业务场景
  2. 调整成本高:参数优化需修改代码重启服务,影响业务连续性
  3. 运行无感知:线程池负载状态缺乏监控,问题爆发后才能察觉

动态线程池参考:springboot整合动态线程池

虚拟线程

虚拟线程是 Java 中的一种轻量级线程,它旨在解决传统线程模型中的一些限制,提供了更高效的并发处理能力,允许创建数千甚至数万个虚拟线程,而无需占用大量操作系统资源。

虚拟线程与传统线程具有如下几个优势:

  1. 更加轻量级:虚拟线程相比传统线程更加轻量级。因为它们不是直接映射到操作系统线程上,而是在用户空间内被管理。这种设计减少了线程创建和销毁的开销,允许在同一应用中运行成千上万的线程。
  2. 资源消耗更少:由于不是直接映射到操作系统线程,虚拟线程显著降低了内存和其他资源的消耗。这使得在有限资源下可以创建更多的线程。
  3. 上下文切换开销更低:由于虚拟线程在用户空间,而不是通过操作系统,所以它的上下文切换开销更低。
  4. 改善阻塞操作的处理:在传统线程模型中,阻塞操作(如 I/O)会导致整个线程被阻塞,浪费宝贵的系统资源。然而当一个虚拟线程阻塞时,它可以被挂起,底层的操作系统线程则可以用来运行其他虚拟线程。
  5. 简化并发编程:可以像编写普通顺序代码一样编写并发代码,而不需要过多考虑线程管理和调度。它简化了 Java 的并发编程模型。
  6. 提升性能:在 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();}

压测结果如下: 恐怖如斯!!!!

在这里插入图片描述

http://www.dtcms.com/a/569194.html

相关文章:

  • App通信:HTTP与JSON全解析
  • 网站推广什么意思资料网站怎么做的
  • win10本地部署weknora记录
  • 7、webgl 基本概念 + 前置数学知识点(向量 + 矩阵)
  • 寻花问柳专做男人的网站高端网站建设设计公司哪家好
  • Rust开发实战之RESTful API客户端开发
  • C++ 锁类型大全详解
  • 智慧园区:智能管理赋能未来发展新生态
  • 潮州 网站建设个人静态网站首页怎么做
  • 东莞网站建站推广wordpress导入演示数据
  • socket_udp
  • 基于单片机的智能家居窗帘控制系统设计(论文+源码)
  • Nestjs框架: 微服务架构拆分原则与实战指南
  • WinSCP的简单使用与SFTP自动备份 .bat脚本
  • iOS 虚拟位置设置实战,多工具协同打造精准调试与场景模拟环境
  • Qt 全球峰会 2025:中国站速递 —— 技术中立,拥抱更大生态
  • Android集成Unity避坑指南
  • 我的网站设计联盟网站推广营销应该怎么做
  • 从零开始刷算法-栈-括号匹配
  • 走进Linux的世界:初识进程(Task)
  • 首钢建设集团山东公司网站2017年网站建设公司
  • 让数据库更智能-大模型如何优化我们的SQL查询
  • 什么程序做网站容易优化apache和wordpress
  • NLP自然语言处理Bert大模型系列学习
  • 数据科学每日总结--Day10--数据库
  • 【实战】自然语言处理--长文本分类(3)HAN算法
  • 中国建设工程招投网站网站后台登陆口
  • 学校网站建设招聘电商推广计划
  • Ubuntu 20.04 系统库管理详细教程
  • [jmeter-商城测试]