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


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

    早上看到群友问到一个关于虚拟线程遇到MySQL连接不兼容导致的性能问题:

    这个问题确实之前就有看到过相关的评测,顺着个这个问题,重新把相关评测找出来,给大家分享一下。

    以下内容主要参考文章:https://medium.com/deno-the-complete-reference/springboot-physical-vs-virtual-threads-vs-webflux-performance-comparison-for-jwt-verify-and-mysql-23d773b41ffd

    评测案例

    评测采用现实场景中的处理流程,具体如下:

    1. 从HTTP授权标头(authorization header)中提取 JWT
    2. 验证 JWT 并从中提取用户的电子邮件
    3. 使用提取到的电子邮件执行 MySQL 查询用户
    4. 返回用户记录

    这个场景其实是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 {
    
    }
    

    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 = 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 {
    
    }
    

    业务逻辑实现:

    @Service
    public class UserService {
    
      @Autowired
      UserRepository userRepository;
    
      public Mono 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 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。第一时间了解前沿行业消息、分享深度技术干货、获取优质学习资源

  • 相关阅读:
    基于MFC——C++课程设计《学生信息管理系统》
    java培训技术数据绑定流程原理
    Oracle数据库环境变量配置以及可能遇到的问题解决
    序列化_原理与应用
    Linux 安装 cuda
    剑指 Offer II 014. 字符串中的变位词 滑动窗口
    C++入门:C语言到C++的过渡
    LeetCode --- 1380. Lucky Numbers in a Matrix 解题报告
    Mysql JSON对象和JSON数组查询
    9、阿里云 Ubuntu22.04、安装docker、mysql、mongodb
  • 原文地址:https://www.cnblogs.com/didispace/p/17973718