來(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 publicTomcatProtocolHandlerCustomizer>protocolHandlerVirtualThreadExecutorCustomizer(){ 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):
所用時(shí)間對(duì)比 每秒請(qǐng)求數(shù) 最小延遲 10%延遲 25%延遲 平均延遲 中位數(shù)延遲 75%延遲 90%延遲 99%延遲 最高延遲 平均CPU使用率 平均內(nèi)存使用率
分析
在此設(shè)置中,即使用MySQL驅(qū)動(dòng)程序時(shí),虛擬線程提供的性能最低、Webflux保持遙遙領(lǐng)先。
審核編輯:湯梓紅
-
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)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論