Spring Boot虚拟线程的性能还不如Webflux?

2024年 1月 10日 131.5k 0

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

测试场景

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

  • 从授权头信息中提取JWT
  • 验证JWT并从中提取用户的Email
  • 使用用户的Email去MySQL里执行查询
  • 返回用户记录
  • 测试技术

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

  • 带有虚拟线程的Spring Boot:这不是一个跑在传统物理线程上的Spring Boot应用,而是跑在虚拟线程上的。这些轻量级线程简化了开发、维护和调试高吞吐量并发应用程序的复杂任务。虽然虚拟线程仍然在底层操作系统线程上运行,但它们带来了显著的效率改进。当虚拟线程遇到阻塞 I/O 操作时,Java 运行时会暂时挂起它,从而释放关联的操作系统线程来为其他虚拟线程提供服务。这个优雅的解决方案优化了资源分配并增强了整体应用程序响应能力。
  • 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 {
    
    }

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

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

    @Service
    public class UserService {
    
      @Autowired
      UserRepository userRepository;
    
      public Mono 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 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?不知道大家是否有做过相关调研呢?如果有的话,欢迎在留言区一起聊聊~我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论