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

2023-09-19 12:30:46

早上看到一篇关于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还是虚拟线程,这两个都是为了提供程序的高并发能力而生,那么谁更胜一筹呢?下面一起看看具体的测试。

这里顺手给大家推荐下我们自研的Youtube视频语音转换插件(https://youtube-dubbing.com/),一键外语转中文,英语不好的小伙伴也可以轻松的学习油管上的优质教程了,下面是演示视频,可以直观的感受一下:

测试环境

运行环境与工具

  • 一台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。

具体结果如下三张图:

0891ffef6a12431973555385aaf78bf8.png
50并发连接
83ba70feae9b52b5832f38f803704494.png
100并发连接
205cf40086cebadf05a81944484d708f.png
300并发连接

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

7d76701cbf624b11abd9b312530481cf.png

似乎引入了虚拟线程还不如已经在用的Webflux?不知道大家是否有做过相关调研呢?如果有的话,欢迎在留言区一起聊聊~我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。

如果您对这篇内容的原文感兴趣的话,可以通过这个链接直达:

https://medium.com/deno-the-complete-reference/springboot-virtual-threads-vs-webflux-performance-comparison-for-jwt-verify-and-mysql-query-ff94cf251c2c

推荐阅读

··································

点击卡片关注程序猿DD,分享一线干货

更多推荐

java基于微信小程序的讲座预约系统的研究与实现

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌文章目录1简介2技术栈第三章系统分析3.1初步需求分析3.2系统用例分析3.2.1公告管理用例分析3.2.2系统管理用例分析3.2.3学生信息用例分析3

MyBatis快速入门

Mybatis概述Mybatis概念MyBatis是一款优秀的持久层框架,用于简化JDBC开发MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为MyBatis。2013年11月迁移到Github官网:ht

nginx配置指南

nginx.conf配置找到Nginx的安装目录下的nginx.conf文件,该文件负责Nginx的基础功能配置。配置文件概述Nginx的主配置文件(conf/nginx.conf)按以下结构组织:配置块功能描述全局块与Nginx运行相关的全局设置events块与网络连接有关的设置http块代理、缓存、日志、虚拟主机等

Android9底部导航栏出现空白按钮问题分析

Android9底部导航栏出现空白按钮问题分析底部导航栏的初始化进入NavigationBarView初始化:进入NavigationBarView的onFinishInflater进入NavigationBarInflaterViewNavigationBarInflaterView加载单个的button回到Navi

XUI - 一个简洁而优雅的Android原生UI框架

官网GitHub-xuexiangjys/XUI:💍AsimpleandelegantAndroidnativeUIframework,freeyourhands!(一个简洁而优雅的Android原生UI框架,解放你的双手!)XUI|💍AsimpleandelegantAndroidnativeUIframewor

【算法】相向双指针

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。推荐:kuan的首页,持续学习,不断总结,共同进步,活到老学到老导航檀越剑指大厂系列:全面总结java核心技术点,如集合,jvm,并发编程redis,kaf

NSSCTF web 刷题记录2

文章目录前言题目[广东强网杯2021团队组]love_Pokemon[NCTF2018]Easy_Audit[安洵杯2019]easy_web[NCTF2018]全球最大交友网站prize_p2[羊城杯2020]easyser[FBCTF2019]rceservice方法一方法二[WUSTCTF2020]颜值成绩查询前

SpringCLoud——服务的拆分和远程调用

服务拆分服务拆分注意事项一般是根据功能的不同,将不同的服务按照功能的不同而分开。微服务拆分注意事项不同微服务,不要重复开发相同业务微服务数据独立,不要访问其他微服务的数据库微服务可以将自己的业务暴露为接口,供其他微服务调用远程调用对于远程调用,之前我们说过,微服务之所以不能像单个服务那样互相的调用各自服务的信息,是因为

REC 系列 Visual Grounding with Transformers 论文阅读笔记

REC系列VisualGroundingwithTransformers论文阅读笔记一、Abstract二、引言三、相关工作3.1视觉定位3.2视觉Transformer四、方法4.1基础的视觉和文本编码器4.2定位编码器自注意力的文本分支文本引导自注意力的视觉分支4.3定位解码器定位query自注意力编码器-解码器自

告别加班烦恼:前端表格构建公式树的绝妙应用!

还在为满屏的公式而“内牛满面”吗?还在为长串的公式解析而发愁吗?还在定位错误的公式而苦恼吗?上班要写代码,加班还要分析这又长又臭的公式。你的发际线还好吗?本葡萄来拯救你的发际线啦!带来的不是洗发水,而是公式追踪!这一章,让本葡萄带你用前端电子表格的公式追踪构建公式树,快(解)速(救)分(你)析(的)公(脱)式(发)问题

【物联网】ROM、RAM和FLASH的区别

引言在计算机领域,我们经常听到ROM、FLASH和RAM这些术语,它们是计算机中不同类型的存储器。虽然它们都用于存储数据,但它们之间有着明显的区别。本文将详细介绍ROM、FLASH和RAM的区别,并给出具体的例子和解释。文章目录引言ROM(只读存储器)RAM(随机存储器)FLASH(闪存)总结ROM(只读存储器)ROM

热文推荐