⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 程序猿DD 「DD」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。

测试场景

作者采用了一个尽可能贴近现实操作的场景:

  1. 从授权头信息中提取JWT
  2. 验证JWT并从中提取用户的Email
  3. 使用用户的Email去MySQL里执行查询
  4. 返回用户记录

测试技术

这里要对比的两个核心技术点是:

  1. 带有虚拟线程的Spring Boot:这不是一个跑在传统物理线程上的Spring Boot应用,而是跑在虚拟线程上的。这些轻量级线程简化了开发、维护和调试高吞吐量并发应用程序的复杂任务。虽然虚拟线程仍然在底层操作系统线程上运行,但它们带来了显着的效率改进。当虚拟线程遇到阻塞 I/O 操作时,Java 运行时会暂时挂起它,从而释放关联的操作系统线程来为其他虚拟线程提供服务。这个优雅的解决方案优化了资源分配并增强了整体应用程序响应能力。
  2. Spring Boot Webflux:Spring Boot WebFlux是Spring生态系统中的反应式编程框架,它利用Project Reactor库来实现非阻塞、事件驱动的编程。所以,它特别适合需要高并发和低延迟的应用程序。依靠反应式方法,它允许开发人员有效地处理大量并发请求,同时仍然提供与各种数据源和通信协议集成的灵活性。

不论是Webflux还是虚拟线程,这两个都是为了提供程序的高并发能力而生,那么谁更胜一筹呢?下面一起看看具体的测试。

测试环境

运行环境与工具

  • 一台16G内存的MacBook Pro M1
  • Java 20
  • Spring Boot 3.1.3
  • 启用预览模式,以获得虚拟线程的强大能力
  • 依赖的第三方库:jjwt、mysql-connector-java
  • 测试工具:Bombardier
  • 数据库:MySQL

数据准备

  • 在Bombardier中准备100000个JWT列表,用来从中随机选取JWT,并将其放入HTTP请求的授权信息中。
  • 在MySQL中创建一个users表,表结构如下:
mysql> desc users;
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| email | varchar(255) | NO | PRI | NULL | |
| first | varchar(255) | YES | | NULL | |
| last | varchar(255) | YES | | NULL | |
| city | varchar(255) | YES | | NULL | |
| county | varchar(255) | YES | | NULL | |
| age | int | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
  • 为users表准备100000条用户数据

测试代码

带虚拟线程的Spring Boot程序

application.properties配置文件:

server.port=3000

spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username= testuser
spring.datasource.password= testpwd
spring.jpa.hibernate.ddl-auto= update
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

User实体类(为了让文章让简洁一些,这里DD省略了getter和setter):

@Entity
@Table(name = "users")
public class User {
@Id
private String email;

private String first;

private String last;

private String city;

private String county;

private int age;

}

应用主类:

@SpringBootApplication
public class UserApplication {

public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}

@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}

提供CRUD操作的UserRepository:

import org.springframework.data.repository.CrudRepository;
import com.example.demo.User;

public interface UserRepository extends CrudRepository<User, String> {

}

提供API接口的UserController类:

@RestController
public class UserController {

@Autowired
UserRepository userRepository;

private SignatureAlgorithm sa = SignatureAlgorithm.HS256;
private String jwtSecret = System.getenv("JWT_SECRET");

@GetMapping("/")
public User handleRequest(@RequestHeader(HttpHeaders.AUTHORIZATION) String authHdr) {
String jwtString = authHdr.replace("Bearer","");
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret.getBytes())
.parseClaimsJws(jwtString).getBody();

Optional<User> user = userRepository.findById((String)claims.get("email"));
return user.get();
}
}

Spring Boot Webflux程序

application.properties配置文件:

server.port=3000

spring.r2dbc.url=r2dbc:mysql://localhost:3306/testdb
spring.r2dbc.username=dbser
spring.r2dbc.password=dbpwd

User实体(这里DD也省略了构造函数、getter和setter):

public class User {

@Id
private String email;

private String first;

private String last;

private String city;

private String county;

private int age;

// 省略了构造函数、getter、setter

}

应用主类:

@EnableWebFlux
@SpringBootApplication
public class UserApplication {

public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}

}

提供CRUD操作的UserRepository:

public interface UserRepository extends R2dbcRepository<User, String> {

}

提供根据id查用户的业务类UserService:

@Service
public class UserService {

@Autowired
UserRepository userRepository;

public Mono<User> findById(String id) {
return userRepository.findById(id);
}
}

提供API接口的UserController类:

@RestController
@RequestMapping("/")
public class UserController {

@Autowired
UserService userService;

private SignatureAlgorithm sa = SignatureAlgorithm.HS256;
private String jwtSecret = System.getenv("JWT_SECRET");

@GetMapping("/")
@ResponseStatus(HttpStatus.OK)
public Mono<User> getUserById(@RequestHeader(HttpHeaders.AUTHORIZATION) String authHdr) {
String jwtString = authHdr.replace("Bearer","");
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret.getBytes())
.parseClaimsJws(jwtString).getBody();
return userService.findById((String)claims.get("email"));
}

}

测试结果

接下来案都做了500w个请求的测试,评估的不同并发连接级别包含:50、100、300。

具体结果如下三张图:

50并发连接

100并发连接

300并发连接

最后,作者得出结论:Spring Boot Webflux要更优于带虚拟线程的Spring Boot。

似乎引入了虚拟线程还不如已经在用的Webflux?不知道大家是否有做过相关调研呢?如果有的话,欢迎在留言区一起聊聊~

文章目录
  1. 1. 测试场景
  2. 2. 测试技术
  3. 3. 测试环境
  4. 4. 测试代码
    1. 4.1. 带虚拟线程的Spring Boot程序
    2. 4.2. Spring Boot Webflux程序
  5. 5. 测试结果