Tomcat 配合虛擬執行緒,一種新的程式設計體驗

2023-12-04 12:03:13

Java 21 在今年早些時候的 9 月 19 日就正式釋出,並開始正式引入虛擬執行緒,但是作為 Java 開發生態中老大哥 Spring 並沒有立即跟進,而是在等待了兩個月後的 11 月 29 日,伴隨著 Spring Boot 3.2 版本的釋出,在這個版本中也終於是引入了對虛擬執行緒的支援。

虛擬執行緒的引入標誌著 Java 在現代程式設計世界中對編寫高吞吐量、高並行應用程式提供了更加完美的支援。

本文我就帶著大家一起深入瞭解一波 Tomcat 配合虛擬執行緒會帶來怎樣的效果以及虛擬執行緒對以後使用 Java 開發高吞吐量、高並行應用程式時所帶來的改變。

本文大綱如下,

Tomcat 使用虛擬執行緒

啟用虛擬執行緒

在 Spring Boot 3.2 中,使用 Tomcat 作為 web 容器時,啟用虛擬執行緒只需要將 spring.threads.virtual.enabled 屬性設定為 true。

這樣 Spinrg Boot 在啟動 Tomcat 容器時會使用一個虛擬執行緒執行器來代表原有的平臺執行緒池。

注意這裡是虛擬執行緒執行器,不是虛擬執行緒池哦。

原始碼解析

在 Spring Boot 3.2 版本以前,Tomcat 預設的執行緒池使用的就是 Java 提供的 ThreadPoolExecutor 執行緒池,在 3.2 版本以後,Spring Boot 修改了建立執行緒池的方法如下所以,

可以看到 Tomcat 會先判斷是否啟用了虛擬執行緒,啟用了的話就直接建立一個虛擬執行緒執行器 VirtualThreadExecutor

VirtualThreadExecutor 類是 Tomcat 為了使用虛擬執行緒作為執行器而新增的。他的內部程式碼中針對每個請求任務都是依賴 Jre21Compat 類處理的。

Jre21Compat 類則是 Tomcat 為了相容 Java21 版本虛擬執行緒新增的一個相容類。這個類利用反射方法來呼叫 Thread.ofVirtual().start(() -> {}) 方法,以便進行任務處理,程式碼截圖如下,

雖然以上程式碼可以啟用 Tomcat 的虛擬執行緒支援。但是在 Spring Boot 中其實不是這樣設定的。還記得上文提到的在 Spring Boot 3.2 中,使用 Tomcat 作為 web 容器時,啟用虛擬執行緒只需要將 spring.threads.virtual.enabled 屬性設定為 true 嗎?

Spring Boot 3.2 中是通過 tomcatVirtualThreadsProtocolHandlerCustomizer 方法來相容虛擬執行緒啟用邏輯的,@ConditionalOnThreading(Threading.VIRTUAL) 條件用判斷 spring.threads.virtual.enabled 屬性是否啟用。程式碼如下,

到這裡其實本文所需要講的涉及原始碼的部分就全部講完了。可以看到 Tomcat 引入虛擬執行緒並不複雜,引入後不在需要維護執行緒池,減輕了執行器的複雜度。

虛擬執行緒帶來的改變

不知道大家注意到原始碼中一個改變沒有,就是在 Spring Boot 3.2 中,啟用了虛擬執行緒後,Tomcat 預設使用的虛擬執行緒執行器不在需要池化。

也就是說,在 Spring Boot 3.2 以後的版本里,我們不在需要設定 server.tomcat.threads.max 以及 server.tomcat.threads.min-spare 兩個屬性以控制 Tomcat 執行緒池的大小了,因為它壓根沒有使用平臺執行緒池。

對於 Tomcat 來說,引入虛擬執行緒,不必在為執行緒池的維護而費心,還能減輕程式設計的複雜度。

虛擬執行緒由 JVM 平臺負責進行排程,它是廉價且輕量級的,Tomcat 可以使用 「每個請求一個執行緒」 模型,而不必擔心實際需要多少個執行緒。

就算請求任務在虛擬執行緒中呼叫阻塞 I/O 操作,導致執行時虛擬執行緒被掛起阻塞,但是隻要掛起結束後該虛擬執行緒就可以恢復。

使用了虛擬執行緒後,程式設計師使用普通的阻塞 API,也可以讓程式對硬體的利用達到近乎完美水平,以此提供高水平的並行性,從而實現高吞吐量。

可以說,虛擬執行緒的引入,以後程式設計師就算是使用 Java 中阻塞 API 也可以開發出高效能、高吞吐量的應用程式。

jmter 實測

在本文中,我還將給各位展示一波 newbeepro 專案升級到 Spring Boot 3.2 後啟用虛擬執行緒所帶來的效能提升。

測試伺服器

  • 主機名稱 VM-16-5-centos
  • 發行版本 centos-7.9.2009
  • 核心版本 3.10.0-1160.88.1.el7.x86_64
  • 系統型別 x86_64
  • 系統設定:2 核 4 G 5M 頻寬

測試專案

newbee-mall-pro 是 newbee-mall 商城的 pro 版本實現了推薦演演算法、商品秒殺、優惠卷使用,滾軸驗證碼,支付寶支付,中文分詞檢索等高階功能。

專案地址:https://github.com/wayn111/newbee-mall-pro

測試方法

使用 newbee-mall-pro 作為測試專案將啟用虛擬執行緒以及未啟用虛擬執行緒的兩次設定部署到測試伺服器上。

啟動容器:amazoncorretto:21.0.1

啟動引數:java -jar -Xms1024m -Xmx1024m /opt/newbeemall/newbee-mall.jar

部署後測試地址:http://62.234.206.94/newbeemall/index

測試介面為秒殺介面:/newbeemall/seckill/2/c81e728d9d4c2f636f067f89cc14862c/executionFour

壓測設定:啟用 2000 個執行緒,每個執行緒迴圈執行 30 秒左右。一共測試五輪,先預熱 JVM 後,取吞吐量最大值。

測試資料

啟用虛擬執行緒

壓測結果如下,

可以看到 CPU 佔用達到百分之 142,記憶體佔用達到百分之 35 的情況下,壓測吞吐量最大可以達到 1731。

不啟用虛擬執行緒

考慮到有 2000 個執行緒進行壓測,所以將 Tomcat 執行緒池的最大執行緒數也設定到 2000,如下圖,

壓測結果如下,

可以看到 CPU 佔用達到百分之 170,記憶體佔用達到百分之 35 的情況下,壓測吞吐量可以達到 1492。


OK,到這裡我們可以看到在 Spring Boot 3.2 版本中,使用了虛擬執行緒的 Tomcat 對比不用虛擬執行緒時,吞吐量提升差不多有 20%。

在更高並行的測試中,這個差距會越來越明顯。因為 Tomcat 使用的平臺執行緒過多時,上下文切換開銷會越來越大,而且虛擬執行緒比平臺執行緒佔用更少的記憶體,一個虛擬執行緒只佔用幾 kb 到幾十 kb 記憶體。可以輕鬆建立上萬虛擬執行緒,降低資源佔用同時提高並行。

最後聊兩句

虛擬執行緒帶給了現代程式設計師新的程式設計體驗,使用阻塞程式設計也能開發出高效能應用程式,而避免了非同步模型的程式設計複雜度,隨著更多的框架接入虛擬執行緒,相信虛擬執行緒會在未來大放異彩。

關注公眾號【waynblog】每週分享技術乾貨、開源專案、實戰經驗、國外優質文章翻譯等,您的關注將是我的更新動力!