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

这里顺手给大家推荐下我们自研的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=3000spring.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 {@Idprivate 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);}@Beanpublic 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 {@AutowiredUserRepository 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=3000spring.r2dbc.url=r2dbc:mysql://localhost:3306/testdb
spring.r2dbc.username=dbser
spring.r2dbc.password=dbpwd

User实体(这里DD也省略了构造函数、getter和setter):

public class User {@Idprivate 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 {@AutowiredUserRepository userRepository;public Mono<User> findById(String id) {return userRepository.findById(id);}
}

提供API接口的UserController类:

@RestController
@RequestMapping("/")
public class UserController {@AutowiredUserService 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

推荐阅读

  • 一招解决Vercel站点在国内无法访问的问题

  • HTTPS是如何运作的?

  • 什么是API网关?为什么要用API网关?

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/140307.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

排序算法:非比较排序(计数排序)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关排序算法的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

三子棋小游戏(简单详细)

设计总体思路 实现游戏可以一直玩&#xff0c;先打印棋盘&#xff0c;玩家和电脑下棋&#xff0c;最后分出胜负。 如果编写较大的程序&#xff0c;我们可以分不同模块 例如这个三子棋&#xff0c;我们可以创建三个文件 分别为&#xff1a; game.h 函数的声明game.c 函数…

Linux 快捷键

1、快捷键小操作 1.1、ctrl c 强制停止 Linux某些程序的运行&#xff0c;如果想要强制停止它&#xff0c;可以使用快捷键ctrl c 命令输入错误&#xff0c;也可以通过快捷键ctrl c&#xff0c;退出当前输入&#xff0c;重新输入 1.2、ctrl d 退出或登出 可以通过快捷键&…

python的requests响应请求,结果乱码,即使设置了response.encoding也没有用的解决方法

一、问题 如图&#xff1a; 一般出现乱码&#xff0c;我们会有三种解决方式&#xff0c;如下但是图中解决了发现还是不行, response.encodingresponse.apparent_encoding通过看网页源码对response.encodingutf8指定编码格式或者直接通过response.content.decode()来获得源码 出…

辨析常见的医学数据分析(相关性分析回归分析)

目录 1 常见的三种分类结果&#xff1f; 2 什么是相关性分析&#xff1f; 相关性分析的结果怎么看&#xff1f; 3 什么是回归分析&#xff1f; 1&#xff09;前提 2&#xff09;常见的回归模型 4 对于存在对照组实验的医学病例如何分析&#xff1f; 1&#xff09;卡方检验…

【Newman+Jenkins】实施接口自动化测试

一、是什么Newman Newman就是纽曼手机这个经典牌子&#xff0c;哈哈&#xff0c;开玩笑啦。。。别当真&#xff0c;简单地说Newman就是命令行版的Postman&#xff0c;查看官网地址。 Newman可以使用Postman导出的collection文件直接在命令行运行&#xff0c;把Postman界面化运…

pytest框架前后置设置,以及pytest默认规则

一、pytest框架的默认规则 1、模块名默认必须以test开头或者以test结尾 2、测试类必须以Test开头&#xff0c;并且不能有__init__方法 3、测试方法默认必须以test开头 当然以后的一些默认规则除测试类不能使用__init__方法外其余的都是可配置的&#xff0c;当然一般情况下我们…

C/C++好题分享--代码题

2-1排序子序列 int main() {int n;cin >> n;// 注意这里多给了一个值&#xff0c;是处理越界的情况的比较&#xff0c;具体参考上面的解题思路vector<int> a;a.resize(n 1);//这里有个坑&#xff0c;这个题越界了牛客测不出来&#xff0c;给n,并且不写a[n] 0;不会…

SpringCloud Gateway--Predicate/断言(详细介绍)上

&#x1f600;前言 本篇博文是关于SpringCloud Gateway–Predicate/断言&#xff08;详细介绍&#xff09;上&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以…

Vue中的路由介绍以及Node.js的使用

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Vue》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这个专栏…

数据结构之顺序表

前言 顺序表采用模块化编程思路&#xff0c;顺序表的实现使用3个模块&#xff0c;test.c—测试模块 Seqlist.c—接口函数的实现模块 seqlist.h—接口函数声明 顺序表的基本概念 顺序表是在计算机内存中通常以数组形式存储的线性表&#xff0c;线性表是n个具有相同特性的数据元…

R语言绘制PCA双标图、碎石图、变量载荷图和变量贡献图

1、原论文数据双标图 代码&#xff1a; setwd("D:/Desktop/0000/R") #更改路径#导入数据 df <- read.table("Input data.csv", header T, sep ",")# ----------------------------------- #所需的包: packages <- c("ggplot2&quo…

电视访问群晖共享文件失败的设置方式,降低协议版本

控制面板-文件服务-SMB-高级设置&#xff0c;常规及其他里面配置即可。

【红外图像增强】基于引力和侧向抑制网络的红外图像增强模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

计算机视觉: 三维物体生成

三维物体生成与编辑 论文地址: Controllable Mesh Generation Through Sparse Latent Point Diffusion Models 背景 数据是目前数字化和AI领域最宝贵的财富之一&#xff0c;但是对于目前的开发者来说&#xff0c;收集数据都意味着极大的成本。所以建立一个高效的生成模型能极…

Linux学习之Redis使用

搭建Redis服务器 在主机redis64运行redis服务 #安装redis服务 [rootredis64 ~]# yum install -y redis # 启动redis服务并开机启动 [rootredis64 ~]# systemctl enable redis --now # 查看redis端口 [rootredis64 ~]# ss -tnlp | grep redis-server LISTEN 0 128 …

PythonWeb服务器(HTTP协议)

一、HTTP协议与实现原理 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一种用于在网络上传输超文本数据的协议。它是Web应用程序通信的基础&#xff0c;通过客户端和服务器之间的请求和响应来传输数据。在HTTP协议中连接客户与服务器的…

网工基础知识——以太网

1972年Bob Metcalfe“以太网之父”被Xerox雇佣为网络专家&#xff0c;Bob Metcalfe 来到Xerox公司的Palo Alto研究中心&#xff08;PARC&#xff09;的第一个任务是把Palo Alto的计算机连接到ARPANET&#xff08;Internet的前身&#xff09;上。1972年底Bob Metcalfe以ALOHA系统…

Day 02 python学习笔记

python运算符 算术运算符 混合运算的优先级&#xff1a; () 高于 ** * / // % 高于 - 赋值运算符 - * / ** a 1 > a 3 > a a 3 其余同理 注意&#xff1a; python没有自增自减 &#xff08;a a a-- --a&#xff09;…

力扣刷题-链表-设计链表

题意&#xff1a; 在链表类中实现这些功能&#xff1a; get(index)&#xff1a;获取链表中第 index 个节点的值。如果索引无效&#xff0c;则返回-1。 addAtHead(val)&#xff1a;在链表的第一个元素之前添加一个值为 val 的节点。插入后&#xff0c;新节点将成为链表的第一个节…