MySQL驱动扯后腿?SpringBoot用虚拟线程可能比用物理线程还差

哥们看看码农 2024-01-20 12:01:09

之前已经分享过多篇关于Spring Boot中使用Java 21新特性虚拟线程的性能测试案例:

Spring Boot 3.2虚拟线程搭建静态文件服务器有多快?Spring Boot 虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较

早上看到群友问到一个关于虚拟线程遇到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=3000spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false&allowPublicKeyRetrieval=truespring.datasource.username= dbuserspring.datasource.password= dbpwdspring.jpa.hibernate.ddl-auto= updatespring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

User实体定义:

@Entity@Table(name = "users")public 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实现:

@RestControllerpublic 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(); }}

应用主类:

@SpringBootApplicationpublic UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); }}#测试代码:使用虚拟线程

主要调整应用主类,其他一样,具体修改如下:

@SpringBootApplicationpublic UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } @Bean public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() { return protocolHandler -> { protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); }; }}#测试代码:使用WebFluxserver.port=3000spring.r2dbc.url=r2dbc:mysql://localhost:3306/testdb?allowPublicKeyRetrieval=true&ssl=falsespring.r2dbc.username=dbuserspring.r2dbc.password=dbpwdspring.r2dbc.pool.initial-size=10spring.r2dbc.pool.max-size=10@Table(name = "users")public 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> {}

业务逻辑实现:

@Servicepublic UserService { @Autowired UserRepository userRepository; public Mono<User> findById(String id) { return userRepository.findById(id); }}

API实现:

@RestController@RequestMapping("/")public 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@SpringBootApplicationpublic 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驱动对虚拟线程支持好的,欢迎留言区推荐一下。

来源:https://www.didispace.com/article/spring-boot/spring-boot-virtual-threads-mysql.html

0 阅读:225

哥们看看码农

简介:感谢大家的关注