环境:SpringBoot3.2.1 + JDK21
从Spring Boot 3.2 支持虚拟线程。要使用虚拟线程,需要在 Java 21 上运行,并将属性 spring.threads.virtual.enabled 设置为 true。
启用虚拟线程后,Tomcat 和 Jetty 将使用虚拟线程处理请求。这意味着处理网络请求的应用程序代码(如控制器中的方法)将在虚拟线程上运行。
启用虚拟线程后,applicationTaskExecutor Bean 将成为配置为使用虚拟线程的 SimpleAsyncTaskExecutor。任何使用应用程序任务执行器的地方,如调用 @Async 方法时的 @EnableAsync、Spring MVC 的异步请求处理和 Spring WebFlux 的阻塞执行支持,现在都将使用虚拟线程。
接下来将分别通过传统阻塞Servlet技术、使用虚拟线程及使用反应式技术WebFlux来分别对比它们的性能。
使用虚拟线程 & 传统Servlet都使用下面的接口:
@RestController@RequestMapping("/task/default")public class TaskDefaultController { @GetMapping("") public Object index() throws Exception { System.out.printf("before - %s%n", Thread.currentThread()) ; TimeUnit.MILLISECONDS.sleep(100) ; System.out.printf("after - %s%n", Thread.currentThread()) ; return "task - default..." ; }}
先测试下启用虚拟线程执行情况。
配置:
spring: threads: virtual: enabled: true
控制台输出:
before - VirtualThread[#42,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1after - VirtualThread[#42,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1
使用的是虚拟线程。
配置线程池,如果不配置使用默认的最大线程200,整体的吞吐量将在2200作用。
server: tomcat: threads: min-spare: 500 max: 1000
初始启动服务后,内存,CPU占用情况;默认启动后线程个数与上面配置一致。
图片
使用jmeter测试,配置如下:
图片
使用500个线程,循环200次,整体做100000次压测。后续的测试都会基于该配置进行。
图片
吞吐量为:4696
内存,CPU占用情况
图片
首先开启虚拟线程
spring: threads: virtual: enabled: true
初始启动服务后,内存,CPU占用情况
图片
jmeter测试情况如下:
图片
吞吐量为:4677,与上面的阻塞Servlet基本差不多。但传统Tomcat线程池方式需要更多的线程才能达到这一值。
图片
整个过程内存使用情况,虚拟线程要比传统Tomcat线程池方式占用的多。
JDK 的虚拟线程调度器是一个工作偷取 ForkJoinPool,以先进先出(FIFO)模式运行。调度器的并行性是指可用来调度虚拟线程的平台线程数。默认情况下,它等于可用处理器的数量,但可以通过系统属性 jdk.virtualThreadScheduler.parallelism 进行调整。ForkJoinPool 与普通池不同,普通池用于并行流的实现,并以后进先出模式运行。
调整数量再进行测试,设置JVM参数
-Djdk.virtualThreadScheduler.parallelism=100 -Djdk.virtualThreadScheduler.maxPoolSize=100
设置100个平台线程来调用虚拟线程。
启动服务后,线程,内存使用情况。
图片
jmeter测试结果如下:
图片
与调整前没什么区别,反而是增加了应用的线程数量。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId></dependency>
基于webflux,我们需要重新编写接口测试。
@RestController@RequestMapping("/task/reactor")public class ReactorController { @GetMapping("") public Object index() throws Exception { // 与上面2种方式不同,reactor方式则需要使用delayElement方式来模拟耗时任务 return Mono.just("task - reactor...").delayElement(Duration.ofMillis(100)) ; }}
初始启动服务后,内存,CPU占用情况。
图片
jmeter测试情况如下:
图片
吞吐量为:4659,与上面的测试结果基本一致。
图片
内存使用情况要比前面几种方式占用都少。同时通过jmeter测试结果也能发现,MAX请求的最大响应时间webflux是最小的,Std.Dev:所有请求响应时间的标准差也是最小的(该值越小,平均值越可靠)。
根据测试结果,虚拟线程与webflux谁更胜一筹还不够清晰,接下来我们结合数据库操作进行测试。
数据库数据准备了600w的数据。
图片
基于JPA进行数据库的操作
@Entity@Table(name = "t_user")public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer uid ; private String name ;}
Repository接口
public interface UserRepository extends JpaRepository<User, Integer> {}
Controller测试接口
@RestController@RequestMapping("/users")public class UserController { @Resource private UserRepository ur ; @GetMapping("/count") public User count() { return ur.findById(5800000).orElse(null) ; } }
测试结果:
图片
记得开启虚拟线程,测试结果如下:
图片
需要引入如下依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId></dependency><dependency> <groupId>com.github.jasync-sql</groupId> <artifactId>jasync-r2dbc-mysql</artifactId> <version>2.1.24</version></dependency>
配置
spring: r2dbc: url: r2dbc:mysql://localhost:3306/batch?serverZnotallow=GMT%2B8&sslMode=DISABLED username: root password: xxxooo pool: initialSize: 100 maxSize: 100 max-acquire-time: 30s max-idle-time: 30m
实体定义,这里的注解与jpa不一样
@Table("t_user")public class User { @Id private Integer uid ; private String name ;}
Repository定义
public interface UserR2DBCRepository extends ReactiveCrudRepository<User, Integer> {}
Controller接口
@RestController@RequestMapping("/r2dbc")public class UserR2DBCController { @Resource private UserR2DBCRepository ur ; @GetMapping("/users") public Mono<User> count() { return ur.findById(5800000) ; } }
测试结果
图片
根据测试结果来,webflux的整体性能远远高于虚拟线程及传统tomcat线程池的方式。
以上是本篇文章全部内容,希望对你有帮助。
完毕!!!
本文链接:http://www.28at.com/showinfo-26-70431-0.htmlSpringBoot3虚拟线程 & 反应式(WebFlux) & 传统Tomcat线程池性能对比
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。
上一篇:C++实现多功能计算器