領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多Project Loom 旨在為 JRE 帶來“易於使用、高吞吐量、輕量級併發”。Project Loom 引入的一項功能是虛擬執行緒。在這篇博文中,我們將透過一些部署在 Apache Tomcat 上的簡單 Web 應用程式,探討虛擬執行緒對 Web 應用程式意味著什麼。
第一個實驗是比較使用Tomcat標準執行緒池和使用基於虛擬執行緒(Loom)的執行器所帶來的開銷。本文末尾詳細介紹了所使用的測試環境。我們透過平均每秒請求數來檢查不同響應大小和請求併發情況下的效能。結果顯示在下圖中。

結果表明,通常情況下,建立新虛擬執行緒來處理請求的開銷小於從執行緒池中獲取平臺執行緒的開銷。
線上程池測試中出現的一個意外結果是,對於較小的響應體,2個併發使用者導致的平均每秒請求數比單個使用者更少,這一點更為明顯。調查發現,額外的延遲發生在任務被傳遞給執行器和執行器呼叫任務的run()方法之間。這種差異在4個併發使用者時有所減小,在8個併發使用者時幾乎消失。
在高併發級別,當併發任務數超過可用的處理器核心數時,虛擬執行緒執行器再次顯示出更高的效能。這在使用較小響應體的測試中更為明顯。
第二個實驗比較了使用Servlet非同步I/O與標準執行緒池的效能,以及使用簡單阻塞I/O與基於虛擬執行緒的執行器的效能。虛擬執行緒在這裡的潛在優勢是簡化。阻塞讀寫比等效的Servlet非同步讀寫要簡單得多——尤其是在考慮錯誤處理時。
Servlet非同步I/O通常用於訪問外部服務,其中響應存在明顯的延遲。測試Web應用程式在Service類中模擬了這種情況。使用基於虛擬執行緒的執行器的Servlet以阻塞方式訪問服務,而使用標準執行緒池的Servlet則使用Servlet非同步API訪問服務。儘管沒有涉及網路I/O,但這應該不會影響結果。
最初的測試,不出所料,阻塞方法和非同步方法之間沒有可測量的差異,因為計時主要由5秒的延遲決定。為了在沒有延遲影響的情況下探索差異,延遲被減少到零,並執行了一組類似於吞吐量測試的測試。結果顯示在下圖中

我們再次看到虛擬執行緒通常效能更高,在低併發和併發超過測試可用的處理器核心數時,差異最為顯著。
基於虛擬執行緒的執行器與Tomcat標準執行緒池之間的差異並不像上面圖表中初看起來那樣明顯。這些測試旨在檢查每種方法所帶來的開銷,並不代表實際應用。在實際應用中,測試中顯示的差異與完成請求所需的時間相比可能微不足道。
Tomcat標準執行緒池與基於虛擬執行緒的執行器之間效能差異的主要驅動因素是向執行緒池佇列新增和移除任務時的爭用。透過最佳化Tomcat當前使用的實現,有可能減少標準執行緒池佇列中的爭用,從而提高吞吐量。
影響相對效能的次要因素是上下文切換。這可能是第二個實驗中,一旦併發度超過可用處理器核心數後,效能差異的合理解釋,因為虛擬執行緒的上下文切換成本低於標準執行緒池中的執行緒。
使用基於虛擬執行緒的執行器是Tomcat標準執行緒池的一個可行替代方案。就容器開銷而言,切換到虛擬執行緒執行器的好處微乎其微。
在Tomcat上遇到阻塞(例如經典Spring MVC)且尚未切換到Servlet非同步API、響應式程式設計或其他非同步API的Web應用程式,透過切換到基於虛擬執行緒的執行器,應該會看到一些可伸縮性改進。根據Web應用程式的不同,這些改進可能無需更改Web應用程式程式碼即可實現。
已切換到使用Servlet非同步API、響應式程式設計或其他非同步API的Web應用程式,切換到基於虛擬執行緒的執行器後,不太可能觀察到可測量的差異(無論是正面的還是負面的)。
從長遠來看,虛擬執行緒最大的好處似乎是簡化應用程式程式碼。目前需要使用Servlet非同步API、響應式程式設計或其他非同步API的一些用例將能夠透過使用阻塞I/O和虛擬執行緒來滿足。但需要注意的是,應用程式通常需要對不同的外部服務進行多次呼叫。這以並行方式進行最有效,雖然像Project Reactor這樣的框架提供了對此的一流支援,但JRE的等效解決方案(結構化併發)仍處於孵化階段,並且僅旨在協調多個Future,而不是以最便捷的方式宣告或組合它們。
最後,Project Loom仍處於預覽模式。現在考慮在生產環境中使用虛擬執行緒還為時過早,但現在是時候將Project Loom和虛擬執行緒納入您的規劃中,以便在JRE中普遍提供虛擬執行緒時做好準備。
測試環境包括以下內容:
測試是在一臺完全更新的Ubuntu 22.04.1 LTS機器上進行的,該機器配備Intel i7-6950X處理器和32 GB RAM。
為了最大限度地顯示測試之間的差異,對預設配置進行了以下更改,以最大限度地減少共同開銷:
測試Web應用程式也旨在最大限度地減少共同開銷並突出測試之間的差異。
使用的server.xml檔案是
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Service name="Catalina">
<Executor
className="org.apache.catalina.core.LoomExecutor"
name="loomExecutor"
/>
<Connector
protocol="org.apache.coyote.http11.Http11NioProtocol"
port="8080"
maxKeepAliveRequests="-1"
/>
<Connector
executor="loomExecutor"
protocol="org.apache.coyote.http11.Http11NioProtocol"
port="8081"
maxKeepAliveRequests="-1"
/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
使用的setenv.sh檔案是
#!/bin/sh
JAVA_OPTS=--enable-preview