來源:程序猿DD
早上看到一篇關于Spring Boot虛擬線程和Webflux性能對比的文章,覺得還不錯。內容較長,抓重點給大家介紹一下這篇文章的核心內容,方便大家快速閱讀。
測試場景
作者采用了一個盡可能貼近現實操作的場景:
從授權頭信息中提取JWT
驗證JWT并從中提取用戶的Email
使用用戶的Email去MySQL里執行查詢
返回用戶記錄
測試技術
這里要對比的兩個核心技術點是:
帶有虛擬線程的Spring Boot:這不是一個跑在傳統物理線程上的Spring Boot應用,而是跑在虛擬線程上的。這些輕量級線程簡化了開發、維護和調試高吞吐量并發應用程序的復雜任務。雖然虛擬線程仍然在底層操作系統線程上運行,但它們帶來了顯著的效率改進。當虛擬線程遇到阻塞 I/O 操作時,Java 運行時會暫時掛起它,從而釋放關聯的操作系統線程來為其他虛擬線程提供服務。這個優雅的解決方案優化了資源分配并增強了整體應用程序響應能力。
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//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?{ }
?
?
@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?=?userRepository.findById((String)claims.get("email"));
????????return?user.get();
????}
}
?
Spring Boot Webflux程序
application.properties配置文件:
?
server.port=3000 spring.r2dbc.url=r2dbc//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?{ }
?
提供根據id查用戶的業務類UserService:
?
@Service
public?class?UserService?{
??@Autowired
??UserRepository?userRepository;
??public?Mono?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?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?不知道大家是否有做過相關調研呢?如果有的話,歡迎在留言區一起聊聊~
審核編輯:湯梓紅
電子發燒友App

































評論