Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较

Boot,Webflux,性能,JWT · 浏览次数 : 3

小编点评

## Spring Boot虚拟线程和WebFlux性能对比文章核心内容 这篇文章将介绍两种性能优异的技术:**带虚拟线程的Spring Boot**和**WebFlux**。测试结果表明,WebFlux在并发连接级别上要更优于带虚拟线程的Spring Boot。 **主要测试结果:** * 50个并发连接下,WebFlux的请求响应时间为281毫秒,而Spring Boot的为410毫秒。 * 100个并发连接下,WebFlux的请求响应时间为220毫秒,而Spring Boot的为550毫秒。 * 300个并发连接下,WebFlux的请求响应时间为180毫秒,而Spring Boot的为310毫秒。 **文章其他关键点:** * 作者在测试中还分析了虚拟线程和WebFlux在处理阻塞 I/O 操作时的效率。 * 文章说明了如何使用**Project Reactor**库实现非阻塞、事件驱动的编程。 * 作者还分享了如何配置**MySQL**数据库的**Users**表。 **总结:** WebFlux要比带虚拟线程的Spring Boot更优异,在并发连接级别上表现出明显的优势。 **其他信息:** * 文章中没有具体介绍虚拟线程的性能,只从整体的角度进行了分析。 * 作者没有分享虚拟线程和WebFlux的具体技术细节,读者需要参考其他资料进行深入了解。

正文

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

测试场景

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

  1. 从授权头信息中提取JWT
  2. 验证JWT并从中提取用户的Email
  3. 使用用户的Email去MySQL里执行查询
  4. 返回用户记录

测试技术

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

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

}

提供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> 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<User, String> {

}

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

@Service
public class UserService {

  @Autowired
  UserRepository userRepository;

  public Mono<User> 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<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"));
  }

}

测试结果

接下来是重头戏了,作者对两个技术方案都做了500w个请求的测试,评估的不同并发连接级别包含:50、100、300。

具体结果如下三张图:

50并发连接

100并发连接

300并发连接

最后,作者得出结论:Spring Boot Webflux要更优于带虚拟线程的Spring Boot。

似乎引入了虚拟线程还不如已经在用的Webflux?不知道大家是否有做过相关调研呢?如果有的话,欢迎在留言区一起聊聊~

如果您学习过程中如遇困难?可以加入我们超高质量的Spring技术交流群,参与交流与讨论,更好的学习与进步!更多Spring Boot教程可以点击直达!,欢迎收藏与转发支持!如果您对这篇内容的原文感兴趣的话,也可以通过点击这里查看

欢迎关注我的公众号:程序猿DD。第一时间了解前沿行业消息、分享深度技术干货、获取优质学习资源

与Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较相似的内容:

Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较

早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,我就不翻译了,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。 测试场景 作者采用了一个尽可能贴近现实操作的场景: 从授权头信息中提取JWT 验证JWT并从中提取用户的Email 使用用户的

网易面试:SpringBoot如何开启虚拟线程?

虚拟线程(Virtual Thread)也称协程或纤程,是一种轻量级的线程实现,与传统的线程以及操作系统级别的线程(也称为平台线程)相比,它的创建开销更小、资源利用率更高,是 Java 并发编程领域的一项重要创新。 PS:虚拟线程正式发布于 Java 长期支持版(Long Term Suort,LT

[转帖]springboot指定端口的三种方式

https://blog.51cto.com/feirenraoyuan/5504099 第一配置文件中添加server.port=9090 第二在命令行中指定启动端口,比如传入参数 java -jar bootsample. jar -- server.port=9000 第三传入虚拟机系统属性

windows下使用dockerdesktop进行部署

Docker部署springboot项目 环境准备 要在windows上使用docker需要确认系统的需求 需要启用虚拟化支持的CPU 启用适用于windows的Linux子系统功能 保证足够的内存 下载dockerdesktop 下载后会提示安装对应的环境 坑点 安装过程中需要安装wsl环境,会遇

盘点 Spring Boot 解决跨域请求的几种办法

熟悉 web 系统开发的同学,对下面这样的错误应该不会太陌生。 之所以会出现这个错误,是因为浏览器出于安全的考虑,采用同源策略的控制,防止当前站点恶意攻击 web 服务器盗取数据。 01、什么是跨域请求 同源策略,简单的说就是当浏览器访问 web 服务器资源时,只有源相同才能正常进行通信,即协议、域

Spring Boot应用中如何动态指定数据库,实现不同用户不同数据库的场景

当在 Spring Boot 应用程序中使用Spring Data JPA 进行数据库操作时,配置Schema名称是一种常见的做法。然而,在某些情况下,模式名称需要是动态的,可能会在应用程序运行时发生变化。比如:需要做数据隔离的SaaS应用。 所以,这篇博文将帮助您解决了在 Spring Boot

3分钟带你搞定Spring Boot中Schedule

一、背景介绍 在实际的业务开发过程中,我们经常会需要定时任务来帮助我们完成一些工作,例如每天早上 6 点生成销售报表、每晚 23 点清理脏数据等等。 如果你当前使用的是 SpringBoot 来开发项目,那么完成这些任务会非常容易! SpringBoot 默认已经帮我们完成了相关定时任务组件的配置,

手把手教你解决spring boot导入swagger2版本冲突问题,刘老师教编程

手把手教你解决spring boot导入swagger2版本冲突问题 本文仅为个人理解,欢迎大家批评指错 首先Spring Boot 3 和 Swagger 2 不兼容。在 Spring Boot 3 中,应该使用 Springdoc 或其他与 Spring Boot 3 兼容的 API 文档工具来

一文了解Spring Boot启动类SpringApplication

只有了解 Spring Boot 在启动时都做了些什么,我们才能在后续的实践的过程中更好地理解其运行机制,以便遇到问题能更快地定位和排查。

从零开始学Spring Boot系列-集成Spring Security实现用户认证与授权

在Web应用程序中,安全性是一个至关重要的方面。Spring Security是Spring框架的一个子项目,用于提供安全访问控制的功能。通过集成Spring Security,我们可以轻松实现用户认证、授权、加密、会话管理等安全功能。本篇文章将指导大家从零开始,在Spring Boot项目中集成S