

MySQL驱动扯后腿?Spring Boot用虚拟线程可能比用物理线程还差
source link: https://www.didispace.com/article/spring-boot/spring-boot-virtual-threads-mysql.html#%E5%88%86%E6%9E%90%E6%80%BB%E7%BB%93
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

之前已经分享过多篇关于Spring Boot中使用Java 21新特性虚拟线程open in new window的性能测试案例:
早上看到群友问到一个关于虚拟线程遇到MySQL连接不兼容导致的性能问题:

这个问题确实之前就有看到过相关的评测,顺着个这个问题,重新把相关评测找出来,给大家分享一下。
以下内容主要参考文章:https://medium.com/deno-the-complete-reference/springboot-physical-vs-virtual-threads-vs-webflux-performance-comparison-for-jwt-verify-and-mysql-23d773b41ffd
# 评测案例
评测采用现实场景中的处理流程,具体如下:
- 从HTTP授权标头(authorization header)中提取 JWT
- 验证 JWT 并从中提取用户的电子邮件
- 使用提取到的电子邮件执行 MySQL 查询用户
- 返回用户记录
这个场景其实是Spring Boot 虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较open in new window测试的后续。前文主要对比了虚拟线程和WebFlux的,但没有对比虚拟线程与物理线程的区别。所以,接下来的内容就是本文关心的重点:在物理线程和虚拟线程下,MySQL驱动是否有性能优化。
# 测试环境
- Java 20(使用预览模式,开启虚拟线程)
- Spring Boot 3.1.3
- 依赖的第三方库:jjwt、mysql-connector-java
测试工具:Bombardier
采用了开源负载测试工具:Bombardier。在测试场景中预先创建 100,000 个 JWT 列表。
在测试期间,Bombardier 从该池中随机选择了JWT,并将它们包含在HTTP请求的Authorization标头中。
MySQL表结构与数据准备
User表结构如下:
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)
准备大约10w条数据:
mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
| 99999 |
+----------+
1 row in set (0.01 sec)
# 测试代码:使用物理线程
配置文件:
server.port=3000
spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username= dbuser
spring.datasource.password= dbpwd
spring.jpa.hibernate.ddl-auto= update
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
User实体定义:
@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;
// 省略了getter和setter
}
数据访问实现:
public interface UserRepository extends CrudRepository<User, String> {
}
API实现:
@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();
}
}
应用主类:
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
# 测试代码:使用虚拟线程
主要调整应用主类,其他一样,具体修改如下:
@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());
};
}
}
# 测试代码:使用WebFlux
server.port=3000
spring.r2dbc.url=r2dbc:mysql://localhost:3306/testdb?allowPublicKeyRetrieval=true&ssl=false
spring.r2dbc.username=dbuser
spring.r2dbc.password=dbpwd
spring.r2dbc.pool.initial-size=10
spring.r2dbc.pool.max-size=10
@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;
// 省略getter、setter和构造函数
}
数据访问实现:
public interface UserRepository extends R2dbcRepository<User, String> {
}
业务逻辑实现:
@Service
public class UserService {
@Autowired
UserRepository userRepository;
public Mono<User> findById(String id) {
return userRepository.findById(id);
}
}
API实现:
@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"));
}
}
应用主类:
@EnableWebFlux
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
# 测试结果
每次测试都包含 100 万个请求,分别评估了它们在不同并发(50、100、300)水平下的性能。下面是结果展示:













# 分析总结
在这个测试案例中使用了MySQL驱动,虚拟线程的实现方式性能最差,WebFlux依然保持领先。所以,主要原因在于这个MySQL的驱动对虚拟线程不友好。如果涉及到数据库访问的情况下,需要寻找对虚拟线程支持最佳的驱动程序。另外,该测试使用的是Java 20和Spring Boot 3.1。对于Java 21和Spring Boot 3.2建议读者在使用的时候自行评估。
最后,对于MySQL驱动对虚拟线程支持好的,欢迎留言区推荐一下。如果您学习过程中如遇困难?可以加入我们超高质量的Spring技术交流群open in new window,参与交流与讨论,更好的学习与进步!更多Spring Boot教程可以点击直达!open in new window,欢迎收藏与转发支持!
</div
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK