MySQL驱动扯后腿?Spring Boot用虚拟线程可能比用物理线程还差
之前已经分享过多篇关于Spring Boot中使用Java 21新特性虚拟线程的性能测试案例:
早上看到群友问到一个关于虚拟线程遇到MySQL连接不兼容导致的性能问题:

这个问题确实之前就有看到过相关的评测,顺着个这个问题,重新把相关评测找出来,给大家分享一下。
评测案例
评测采用现实场景中的处理流程,具体如下:
- 从HTTP授权标头(authorization header)中提取 JWT
- 验证 JWT 并从中提取用户的电子邮件
- 使用提取到的电子邮件执行 MySQL 查询用户
- 返回用户记录
这个场景其实是Spring Boot 虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较测试的后续。前文主要对比了虚拟线程和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技术交流群,参与交流与讨论,更好的学习与进步!更多Spring Boot教程可以点击直达!,欢迎收藏与转发支持!
欢迎关注我的公众号:程序猿DD。第一时间了解前沿行业消息、分享深度技术干货、获取优质学习资源
MySQL驱动扯后腿?Spring Boot用虚拟线程可能比用物理线程还差的更多相关文章
- spring boot 配置虚拟静态资源文件
我们实现的目的是:通过spring boot 配置静态资源访问的虚拟路径,可实现在服务器,或者在本地通过:http://ip地址:端口/资源路径/文件名 ,可直接访问文件 比如:我们本地电脑的:E: ...
- spring boot 配置虚拟目录
如上图,关键地方有两个: 1.下方的 web.upload-path (配置本地文件路径) 2.上方一串配置,具体代码如下: profiles: include: paperIdentify acti ...
- Spring框架学习笔记(6)——阿里云服务器部署Spring Boot项目(jar包)
最近接外包,需要部署服务器,便是参考了网上的几篇博文,成功在阿里云服务器成功部署了Spring Boot项目,特记下本篇笔记 Spring Boot项目打包 这里说一下部署的一些问题 1.mysql驱 ...
- java并发学习--第二章 spring boot实现线程的创建
除了之前介绍的创建线程方式外,spring boot为我们了提供一套完整的线程创建方式,其中包括了:线程.线程池.线程的监控. 一.使用spring boot提供的方法创建线程与线程池 1.首先在sp ...
- Spring Boot整合ElasticSearch和Mysql 附案例源码
导读 前二天,写了一篇ElasticSearch7.8.1从入门到精通的(点我直达),但是还没有整合到SpringBoot中,下面演示将ElasticSearch和mysql整合到Spring Boo ...
- Spring Boot学习随记
由于早年在管理领域耕耘了一段时间,完美错过了Spring的活跃期, 多少对这个经典的技术带有一种遗憾的心态在里面的, 从下面的我的生涯手绘图中大概可以看出来我的经历. 最近由于新介入到了工业数字化领域 ...
- spring boot基于DRUID数据源密码加密及数据源监控实现
前言 随着需求和技术的日益革新,spring boot框架是越来越流行,她也越来越多地出现在我们的项目中,当然最主要的原因还是因为spring boot构建项目实在是太爽了,构建方便,开发简单,而且效 ...
- RabbitMQ与Spring的框架整合之Spring Boot实战
1.RabbitMQ与Spring的框架整合之Spring Boot实战. 首先创建maven项目的RabbitMQ的消息生产者rabbitmq-springboot-provider项目,配置pom ...
- spring boot + spring security +前后端分离【跨域】配置 + ajax的json传输数据
1.前言 网上各个社区的博客参差不齐 ,给初学者很大的困扰 , 我琢磨了一天一夜,到各个社区找资料,然后不断测试,遇到各种坑,一言难尽啊,要么源码只有一部分,要么直接报错... 最后实在不行,直接去看 ...
- spring boot + mybatis + mybatis逆向工程 --- 心得
1.前言 以前用惯了springMVC框架 ,以SSM 框架 来开发项目 ,现在因为需要,使用spring boot框架,那么mybatis该如何与spring boot结合呢? 结构区别不大,但是 ...
随机推荐
- [python][图像切割]给定手写数字图片完成数字切割
import torch import torch.nn as nn from torchvision import transforms from PIL import Image, ImageOp ...
- 文心一言 VS 讯飞星火 VS chatgpt (154)-- 算法导论12.3 1题
一.用go语言,给出 TREE-INSERT 过程的一个递归版本. 文心一言,代码不能运行: 下面是用 Go 语言实现一个递归版本的 TREE-INSERT 过程的代码: package main i ...
- [ABC274G] Security Camera 3
Problem Statement There is a grid with $H$ rows from top to bottom and $W$ columns from left to righ ...
- 数据仓库主流开发语言——SQL
数仓开发语言概述 SQL语言介绍 数仓与SQL 结构化数据 二维表结构 SQL语法分类
- 开发AI量化策略所遇到的坑
AI只是工具,想要驾驭AI还得自身有点功底,不然反而会被工具所害,甚至从信仰AI变为抵制AI.本文简单介绍开发AI量化选股策略中所遇到的各种坑,希望大家有所收获,少走弯路. 本文为BigQuant用户 ...
- 华企盾DSC造成扫描仪无法扫描成pdf文件
解决方法:去掉缩略图策略,重启电脑
- [BUG]自定义登录引发的配置加载问题-bugspringsecurity
title: [BUG]自定义登录引发的配置加载问题 date: 2021-12-10 16:43:36.311 updated: 2021-12-27 19:10:16.773 url: https ...
- 【经典问题】mysql和redis数据一致性问题
前言 MySQL和Redis数据一致性算是个很经典的问题,在之前也看到过很多相关的文章,最近心血来潮,想把一致性问题的解决方案和存在问题都总结一下. 不推荐方案 1 先更新MySQL,再更新Redis ...
- SpringBoot 接口:响应时间优化9个技巧!
今天聊聊 SpringBoot接口:响应时间优化的9个技巧.在实际开发中,提升接口响应速度是一件挺重要的事,特别是在面临大量用户请求的时候.好了,咱们直接切入正题. 本文,已收录于,我的技术网站 dd ...
- Reactor 简介
官方的介绍如下: Reactor is a fully non-blocking reactive programming foundation for the JVM, with efficient ...