0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

SpringBoot物理線程、虛擬線程、Webflux性能比較

jf_ro2CN3Fa ? 來(lái)源:叢林 medium.com ? 2023-10-23 14:31 ? 次閱讀

來(lái)源:叢林medium.com

大量的文章評(píng)估了一系列技術(shù)(包括 Node.js、Deno、Bun、Rust、Go、Spring、Python 等)在簡(jiǎn)單的“hello world”場(chǎng)景中的性能。雖然這些文章獲得了好評(píng),但有一個(gè)共同點(diǎn):忽略了現(xiàn)實(shí)場(chǎng)景開發(fā)中的復(fù)雜性 。

本文旨在通過(guò)現(xiàn)實(shí)場(chǎng)景的視角剖析各種技術(shù),在這種特殊情況下,我們深入研究以下常見用例:

從 authorization header 中提取一個(gè)JWT。

驗(yàn)證JWT并從聲明中提取用戶的電子郵件。

使用提取的電子郵件執(zhí)行MySQL查詢。

最后,返回用戶的記錄。

雖然這個(gè)場(chǎng)景看起來(lái)似乎也很簡(jiǎn)單,但它概括了 Web 開發(fā)領(lǐng)域中經(jīng)常遇到的現(xiàn)實(shí)挑戰(zhàn)。

介紹

在本文中,我們將深入探討所有同級(jí)產(chǎn)品之間的友好比較,即具有「物理線程、虛擬線程和 Webflux 的 SpringBoot」 ,重點(diǎn)關(guān)注它們?cè)谔囟ㄓ美龍?chǎng)景中的性能。我們已經(jīng)探索了標(biāo)準(zhǔn) SpringBoot 應(yīng)用程序如何與 webflux 相媲美,但現(xiàn)在,我們引入一個(gè)關(guān)鍵的區(qū)別:

帶有虛擬線程的 Spring Boot

我們熟悉 SpringBoot,但有一點(diǎn)不同——它在虛擬線程而不是傳統(tǒng)的物理線程上運(yùn)行。虛擬線程是并發(fā)領(lǐng)域的游戲規(guī)則改變者。這些輕量級(jí)線程簡(jiǎn)化了開發(fā)、維護(hù)和調(diào)試高吞吐量并發(fā)應(yīng)用程序的復(fù)雜任務(wù)。

雖然虛擬線程仍然在底層操作系統(tǒng)線程上運(yùn)行,但它們帶來(lái)了顯著的效率改進(jìn)。當(dāng)虛擬線程遇到阻塞 I/O 操作時(shí),Java 運(yùn)行時(shí)會(huì)暫時(shí)掛起它,從而釋放關(guān)聯(lián)的操作系統(tǒng)線程來(lái)為其他虛擬線程提供服務(wù)。這個(gè)優(yōu)雅的解決方案優(yōu)化了資源分配并增強(qiáng)了整體應(yīng)用程序響應(yīng)能力。

考慮到這些有趣的設(shè)置,讓我們更深入地研究我們的性能比較。撰寫本文是為了解決最常見的請(qǐng)求之一,即查看物理、虛擬和 Webflux 在實(shí)際用例中的比較。

測(cè)試環(huán)境及軟件版本

我們的性能測(cè)試是在配備 16GB RAM 的 MacBook Pro M1 上進(jìn)行的,確保了可靠的測(cè)試平臺(tái)。用于這些測(cè)試的軟件堆棧包括:

SpringBoot 3.1.3(在Java 20上運(yùn)行)

啟用預(yù)覽模式以獲得虛擬線程的強(qiáng)大功能

jjwt用于JWT驗(yàn)證和解碼,增強(qiáng)我們應(yīng)用程序的安全性。

mysql-connector-java 用于執(zhí)行 MySQL 查詢,維護(hù)數(shù)據(jù)完整性和一致性。

負(fù)載測(cè)試和 JWT

為了評(píng)估我們的應(yīng)用程序在不同負(fù)載下的性能,我們使用了開源負(fù)載測(cè)試工具 Bombardier。我們的測(cè)試場(chǎng)景涉及預(yù)先創(chuàng)建的 100000 個(gè) JWT 列表。在測(cè)試過(guò)程中,Bombardier 從該池中隨機(jī)選擇 JWT,并將它們包含在 HTTP 請(qǐng)求的授權(quán)標(biāo)頭中。

MySQL 數(shù)據(jù)庫(kù)架構(gòu)

用于這些性能測(cè)試的 MySQL 數(shù)據(jù)庫(kù)有一個(gè)名為 users 的表。該表設(shè)計(jì)有 6 列,足以模擬我們應(yīng)用程序中的真實(shí)數(shù)據(jù)交互,使我們能夠評(píng)估它們的響應(yīng)能力和可擴(kuò)展性。

mysql>descusers;
+--------+--------------+------+-----+---------+-------+
|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||
+--------+--------------+------+-----+---------+-------+
6rowsinset(0.00sec)

用戶數(shù)據(jù)庫(kù)已準(zhǔn)備好包含 100000 條用戶記錄的初始數(shù)據(jù)集。

mysql>selectcount(*)fromusers;
+----------+
|count(*)|
+----------+
|99999|
+----------+
1rowinset(0.01sec)

在我們對(duì) SpringBoot 物理線程、虛擬線程和 Webflux 進(jìn)行友好性能評(píng)估的背景下,了解關(guān)鍵的數(shù)據(jù)關(guān)系至關(guān)重要。具體來(lái)說(shuō),在JSON Web Token(JWT)有效負(fù)載中,每個(gè)電子郵件條目直接對(duì)應(yīng)于存儲(chǔ)在 MySQL 數(shù)據(jù)庫(kù)中的一條用戶記錄。

代碼

SpringBoot(物理線程)

配置信息

server.port=3000
spring.datasource.url= jdbc//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

實(shí)體類

packagecom.example.demo;

importjakarta.persistence.Entity;
importjakarta.persistence.Table;
importjakarta.persistence.GeneratedValue;
importjakarta.persistence.GenerationType;
importjakarta.persistence.Id;

@Entity
@Table(name="users")
publicclassUser{
@Id
privateStringemail;

privateStringfirst;

privateStringlast;

privateStringcity;

privateStringcounty;

privateintage;

publicStringgetId(){
returnemail;
}

publicvoidsetId(Stringemail){
this.email=email;
}

publicStringgetFirst(){
returnfirst;
}

publicvoidsetFirst(Stringname){
this.first=name;
}

publicStringgetLast(){
returnlast;
}

publicvoidsetLast(Stringname){
this.last=name;
}

publicStringgetEmail(){
returnemail;
}

publicvoidsetEmail(Stringemail){
this.email=email;
}

publicStringgetCity(){
returncity;
}

publicvoidsetCity(Stringcity){
this.city=city;
}

publicStringgetCounty(){
returncounty;
}

publicvoidsetCounty(Stringcounty){
this.county=county;
}

publicintgetAge(){
returnage;
}

publicvoidsetAge(intage){
this.age=age;
}
}

啟動(dòng)類

packagecom.example.demo;

importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
importorg.springframework.context.annotation.Bean;

@SpringBootApplication
publicclassUserApplication{

publicstaticvoidmain(String[]args){
SpringApplication.run(UserApplication.class,args);
}
}

Controller層

packagecom.example.demo;

importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RequestHeader;
importorg.springframework.http.ResponseEntity;
importorg.springframework.http.HttpStatus;
importorg.springframework.http.HttpHeaders;
importorg.springframework.web.bind.annotation.RestController;
importorg.springframework.beans.factory.annotation.Autowired;
importjava.util.Optional;
importio.jsonwebtoken.Jwts;
importio.jsonwebtoken.Jws;
importio.jsonwebtoken.Claims;
importio.jsonwebtoken.SignatureAlgorithm;
importio.jsonwebtoken.security.Keys;
importjava.security.Key;
importcom.example.demo.UserRepository;
importcom.example.demo.User;

@RestController
publicclassUserController{

@Autowired
UserRepositoryuserRepository;

privateSignatureAlgorithmsa=SignatureAlgorithm.HS256;
privateStringjwtSecret=System.getenv("JWT_SECRET");

@GetMapping("/")
publicUserhandleRequest(@RequestHeader(HttpHeaders.AUTHORIZATION)StringauthHdr){
StringjwtString=authHdr.replace("Bearer","");
Claimsclaims=Jwts.parser()
.setSigningKey(jwtSecret.getBytes())
.parseClaimsJws(jwtString).getBody();

Optionaluser=userRepository.findById((String)claims.get("email"));
returnuser.get();
}
}

接口

packagecom.example.demo;

importorg.springframework.data.repository.CrudRepository;
importcom.example.demo.User;

publicinterfaceUserRepositoryextendsCrudRepository{

}

Springboot(虛擬線程)

其余代碼基本照搬上述 「物理線程」 , 啟動(dòng)類修改如下:

packagecom.example.demo;

importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
importorg.springframework.context.annotation.Bean;
importjava.util.concurrent.Executors;

@SpringBootApplication
publicclassUserApplication{

publicstaticvoidmain(String[]args){
SpringApplication.run(UserApplication.class,args);
}

@Bean
publicTomcatProtocolHandlerCustomizerprotocolHandlerVirtualThreadExecutorCustomizer(){
returnprotocolHandler->{
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}

SpringBoot(webflux)

server.port=3000
spring.r2dbc.url=r2dbc//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

啟動(dòng)類

packagewebfluxdemo;

importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.context.annotation.Bean;
importorg.springframework.core.io.ClassPathResource;
importorg.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
importorg.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
importorg.springframework.web.reactive.config.EnableWebFlux;

importio.r2dbc.spi.ConnectionFactory;

@EnableWebFlux
@SpringBootApplication
publicclassUserApplication{

publicstaticvoidmain(String[]args){
SpringApplication.run(UserApplication.class,args);
}

}

Controller層代碼

packagewebfluxdemo;

importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.http.HttpStatus;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.PathVariable;
importorg.springframework.web.bind.annotation.RequestBody;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestParam;
importorg.springframework.web.bind.annotation.ResponseStatus;
importorg.springframework.web.bind.annotation.RestController;
importorg.springframework.web.bind.annotation.RequestHeader;
importorg.springframework.http.HttpHeaders;

importwebfluxdemo.User;
importwebfluxdemo.UserService;

importio.jsonwebtoken.Jwts;
importio.jsonwebtoken.Jws;
importio.jsonwebtoken.Claims;
importio.jsonwebtoken.SignatureAlgorithm;
importio.jsonwebtoken.security.Keys;
importjava.security.Key;

importreactor.core.publisher.Flux;
importreactor.core.publisher.Mono;

@RestController
@RequestMapping("/")
publicclassUserController{
@Autowired
UserServiceuserService;

privateSignatureAlgorithmsa=SignatureAlgorithm.HS256;
privateStringjwtSecret=System.getenv("JWT_SECRET");

@GetMapping("/")
@ResponseStatus(HttpStatus.OK)
publicMonogetUserById(@RequestHeader(HttpHeaders.AUTHORIZATION)StringauthHdr){
StringjwtString=authHdr.replace("Bearer","");
Claimsclaims=Jwts.parser()
.setSigningKey(jwtSecret.getBytes())
.parseClaimsJws(jwtString).getBody();
returnuserService.findById((String)claims.get("email"));
}
}

接口類

packagewebfluxdemo;

importorg.springframework.data.r2dbc.repository.R2dbcRepository;
importorg.springframework.stereotype.Repository;

importwebfluxdemo.User;

publicinterfaceUserRepositoryextendsR2dbcRepository{

}

Service層代碼

packagewebfluxdemo;

importjava.util.Optional;

importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;

importwebfluxdemo.User;
importwebfluxdemo.UserRepository;

importreactor.core.publisher.Flux;
importreactor.core.publisher.Mono;

@Service
publicclassUserService{

@Autowired
UserRepositoryuserRepository;

publicMonofindById(Stringid){
returnuserRepository.findById(id);
}
}

結(jié)果

為了評(píng)估性能,我們進(jìn)行了一系列嚴(yán)格的測(cè)試。每個(gè)測(cè)試由100萬(wàn)個(gè)請(qǐng)求組成,我們?cè)u(píng)估了它們?cè)诓煌l(fā)連接級(jí)別(50、100和300)下的性能。

現(xiàn)在,讓我們深入研究結(jié)果,以圖表形式呈現(xiàn):

1d7bc8ae-6fea-11ee-939d-92fbcf53809c.png所用時(shí)間對(duì)比 1d94cd90-6fea-11ee-939d-92fbcf53809c.png每秒請(qǐng)求數(shù) 1daebb24-6fea-11ee-939d-92fbcf53809c.png最小延遲 1dc5fe88-6fea-11ee-939d-92fbcf53809c.png10%延遲 1de4b120-6fea-11ee-939d-92fbcf53809c.png25%延遲 1e05d51c-6fea-11ee-939d-92fbcf53809c.png平均延遲 1e194a70-6fea-11ee-939d-92fbcf53809c.png中位數(shù)延遲 1e29a0fa-6fea-11ee-939d-92fbcf53809c.png75%延遲 1e4672c0-6fea-11ee-939d-92fbcf53809c.png90%延遲 1e58d6a4-6fea-11ee-939d-92fbcf53809c.png99%延遲 1e6e8288-6fea-11ee-939d-92fbcf53809c.png最高延遲 1e7bc006-6fea-11ee-939d-92fbcf53809c.png平均CPU使用率 1e90a728-6fea-11ee-939d-92fbcf53809c.png平均內(nèi)存使用率

分析

在此設(shè)置中,即使用MySQL驅(qū)動(dòng)程序時(shí),虛擬線程提供的性能最低、Webflux保持遙遙領(lǐng)先。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2952

    瀏覽量

    104479
  • 開源
    +關(guān)注

    關(guān)注

    3

    文章

    3215

    瀏覽量

    42327
  • MySQL
    +關(guān)注

    關(guān)注

    1

    文章

    797

    瀏覽量

    26399
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    503

    瀏覽量

    19634
  • SpringBoot
    +關(guān)注

    關(guān)注

    0

    文章

    173

    瀏覽量

    161

原文標(biāo)題:SpringBoot 物理線程、虛擬線程、Webflux 性能全面對(duì)比!

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Spring Boot虛擬線程Webflux性能對(duì)比

    早上看到一篇關(guān)于Spring Boot虛擬線程Webflux性能對(duì)比的文章,覺得還不錯(cuò)。內(nèi)容較長(zhǎng),抓重點(diǎn)給大家介紹一下這篇文章的核心內(nèi)容,方便大家快速閱讀。
    發(fā)表于 09-24 14:54 ?831次閱讀
    Spring Boot<b class='flag-5'>虛擬</b><b class='flag-5'>線程</b>和<b class='flag-5'>Webflux</b><b class='flag-5'>性能</b>對(duì)比

    .NET8性能優(yōu)化之線程

    目前來(lái)說(shuō),沒(méi)有確切的證據(jù)證明哪個(gè)線程池好用,或者效率更高。但是開發(fā)者可以使用上面的選項(xiàng)來(lái)進(jìn)行自己的選擇,有一個(gè)測(cè)試就是在Windows線程池在比較大的機(jī)器上的IO擴(kuò)展性不太好。如果你的應(yīng)用程序已經(jīng)
    的頭像 發(fā)表于 01-22 14:50 ?1074次閱讀

    CPU與核心及進(jìn)程和線程認(rèn)識(shí)

    所謂的4核8線程,4核指的是物理核心。通過(guò)超線程技術(shù),用一個(gè)物理核模擬兩個(gè)虛擬核,每個(gè)核兩個(gè)線程
    的頭像 發(fā)表于 03-30 14:48 ?8048次閱讀
    CPU與核心及進(jìn)程和<b class='flag-5'>線程</b>認(rèn)識(shí)

    虛擬機(jī):linux 進(jìn)程的最大線程個(gè)數(shù)

    虛擬機(jī):linux 進(jìn)程的最大線程個(gè)數(shù)
    的頭像 發(fā)表于 06-22 15:56 ?2682次閱讀
    <b class='flag-5'>虛擬</b>機(jī):linux 進(jìn)程的最大<b class='flag-5'>線程</b>個(gè)數(shù)

    虛擬機(jī):Linux查看線程信息的步驟

    虛擬機(jī):Linux查看線程信息的步驟
    的頭像 發(fā)表于 06-24 08:41 ?3496次閱讀
    <b class='flag-5'>虛擬</b>機(jī):Linux查看<b class='flag-5'>線程</b>信息的步驟

    SpringBoot實(shí)現(xiàn)多線程

    SpringBoot實(shí)現(xiàn)多線程
    的頭像 發(fā)表于 01-12 16:59 ?1786次閱讀
    <b class='flag-5'>SpringBoot</b>實(shí)現(xiàn)多<b class='flag-5'>線程</b>

    什么是線程線程池中線程實(shí)現(xiàn)復(fù)用的原理

    一般建議自定義線程工廠,構(gòu)建線程的時(shí)候設(shè)置線程的名稱,這樣就在查日志的時(shí)候就方便知道是哪個(gè)線程執(zhí)行的代碼。
    發(fā)表于 01-29 13:44 ?1690次閱讀

    核心線程數(shù)和最大線程數(shù)區(qū)別

    達(dá)到最大線程數(shù)。當(dāng)任務(wù)執(zhí)行完畢后,線程池會(huì)根據(jù)線程池參數(shù)來(lái)決定是否回收線程。 簡(jiǎn)單來(lái)說(shuō),核心線程數(shù)用于優(yōu)化
    的頭像 發(fā)表于 06-01 09:33 ?7532次閱讀

    線程池的線程怎么釋放

    線程分組看,pool名開頭線程占616條,而且waiting狀態(tài)也是616條,這個(gè)點(diǎn)就非??梢闪?,我斷定就是這個(gè)pool開頭線程池導(dǎo)致的問(wèn)題。我們先排查為何這個(gè)線程池中會(huì)有600+的
    發(fā)表于 07-31 10:49 ?2206次閱讀
    <b class='flag-5'>線程</b>池的<b class='flag-5'>線程</b>怎么釋放

    Spring 的線程池應(yīng)用

    。 使用@Async聲明多線程 SpringBoot 提供了注解 @Async 來(lái)使用線程池, 具體使用方法如下: 在啟動(dòng)類(配置類)添加 @EnableAsync 來(lái)開啟線程池 在需
    的頭像 發(fā)表于 10-13 10:47 ?585次閱讀
    Spring 的<b class='flag-5'>線程</b>池應(yīng)用

    什么是虛擬線程?虛擬線程到底是做什么用的呢?

    虛擬線程是在Java并發(fā)領(lǐng)域添加的一個(gè)新概念,那么虛擬線程到底是做什么用的呢?
    的頭像 發(fā)表于 10-29 10:23 ?3036次閱讀
    什么是<b class='flag-5'>虛擬</b><b class='flag-5'>線程</b>?<b class='flag-5'>虛擬</b><b class='flag-5'>線程</b>到底是做什么用的呢?

    線程池基本概念與原理

    一、線程池基本概念與原理 1.1 線程池概念及優(yōu)勢(shì) C++線程池簡(jiǎn)介 線程池是一種并發(fā)編程技術(shù),它能有效地管理并發(fā)的線程、減少資源占用和提高
    的頭像 發(fā)表于 11-10 10:24 ?465次閱讀

    核心線程數(shù)和最大線程數(shù)怎么設(shè)置

    核心線程數(shù)和最大線程數(shù)是Java線程池中重要的參數(shù),用來(lái)控制線程池中線程的數(shù)量和行為。正確地設(shè)置這兩個(gè)參數(shù)可以優(yōu)化系統(tǒng)的
    的頭像 發(fā)表于 12-01 13:50 ?8492次閱讀

    redis多線程還能保證線程安全嗎

    Redis是一種使用C語(yǔ)言編寫的高性能鍵值存儲(chǔ)系統(tǒng),它是單線程的,因?yàn)槭褂昧硕嗦窂?fù)用的方式來(lái)處理并發(fā)請(qǐng)求。這樣的實(shí)現(xiàn)方式帶來(lái)了很好的性能,但同時(shí)也引發(fā)了一些線程安全方面的問(wèn)題。 在Re
    的頭像 發(fā)表于 12-05 10:28 ?1628次閱讀

    探索虛擬線程:原理與實(shí)現(xiàn)

    虛擬線程的引入與優(yōu)勢(shì) 在Loom項(xiàng)目之前,Java虛擬機(jī)(JVM)中的線程是通過(guò)java.lang.Thread類型來(lái)實(shí)現(xiàn)的,這些線程被稱為
    的頭像 發(fā)表于 06-24 11:35 ?243次閱讀
    探索<b class='flag-5'>虛擬</b><b class='flag-5'>線程</b>:原理與實(shí)現(xiàn)