如何對Tomcat進行性能優化?
對于提供接口服務的應用程序來說,很多都使用SpringBoot默認的Servlet容器Tomcat。
上線之初,由于大部分流量較小,我們不會對Tomcat進行特殊的參數調整。
然而,隨著流量的增加,應用的性能指標變得越來越差。這個時候我們大多數人都會選擇擴容。除了擴容之外,我們還可以選擇對Tomcat進行性能調優,在不增加成本的情況下提升性能。
今天我們將分享如何對Tomcat進行簡單的性能調優,以提高應用程序的性能。
tomcat的架構
從上圖可以看出,Tomcat將其業務抽象為Server, Service, Connector, ContAIner等組件,每個組件都有不同的作用。
Server組件是Tomcat的最外層組件,它是Tomcat實例本身的抽象,代表Tomcat本身。一個服務器組件可以有一個或多個服務組件。
Service組件是Tomcat中提供服務和處理請求的一組組件。一個服務組件可以有多個連接器和一個容器。多個Connector表示它可以使用多種協議同時接收用戶請求。
連接器負責處理客戶端連接,它提供對各種服務協議的支持,包括BIO、NIO、AIO等,它存在的價值在于屏蔽多協議Container的復雜性,統一Container的處理標準。
Container組件是負責具體業務邏輯處理的容器。當Connector組件與客戶端建立連接時,它會將請求轉發給Container組件的Engine組件進行處理。
至此,Tomcat的核心組件就基本完成了。其實Container組件里面還有很多細分的組件。如果你對業務的抽象感興趣的話,可以繼續看下去。
- Engine組件代表一個可運行的Servlet實例,包含Servlet容器的核心功能,可以擁有一個或多個虛擬主機(Host)。其主要作用是將請求委托給合適的虛擬主機進行處理,即根據URL路徑的配置來匹配合適的虛擬主機進行處理。
- Host組件負責運行多個應用程序,并且它負責安裝這些應用程序。它的主要功能是解析web.xml文件并匹配到相應的Context組件。
- Context組件代表了具體的Web應用程序本身,它最重要的功能就是管理里面的Servlet實例。一個 Context 可以有一個或多個 Servlet 實例。
- 一個WrApper組件代表一個Servlet,它負責管理一個Servlet,包括Servlet的加載、初始化、執行和資源回收等。包裝器是最低級別的容器。
可以看出,Host是虛擬主機的抽象,Context是應用程序的抽象,Wrapper是Servlet的抽象,Engine是處理層的抽象
核心參數
在了解核心參數之前,我們需要對Tomcat對于請求的處理流程有一個大概的了解。
Tomcat對請求的處理流程如下。
在上面的示意圖中,有三個非常關鍵的核心參數,這也是性能調優的關鍵。
- acceptCount:當Container線程池達到最大數量且沒有空閑線程,且Connector隊列達到最大數量時,操作系統可以接受的最大連接數。
- maxConnections:當Container線程池達到最大數量并且沒有空閑線程時,Connector的隊列可以接收的最大線程數量。
- maxThreads:Container線程池中最大處理線程數。
從以上三個參數的含義,我們可以得知以下結論。
客戶端并不直接與Tomcat的Connector組件建立聯系,而是先與操作系統建立聯系,然后交給Connector。
這一點非常重要,否則你將無法理解該acceptCount參數。
不僅Connector組件中有一個隊列,操作系統中也有一個隊列來臨時存儲與客戶端的連接,這也是一個關鍵點。我們所說的線程池是指Container容器中的線程池。
理解這三個核心參數的含義非常重要,否則就沒有辦法進行后續的性能調優工作。
maxThreads
我們知道指的maxThreads是最大請求處理線程數,Tomcat7和Tomcat8都是默認的200。
該參數的設置需要根據任務的執行內容進行調整。一般來說,計算公式為:最大線程數 = ((IO time + CPU time)/CPU time) * CPU核心數。
這個公式的思想其實很簡單,就是最大化的利用CPU資源。
任務的時間消耗分為IO時間消耗和CPU時間消耗。基本上IO時間消耗是最多的,此時CPU無事可做。
所以如果可以讓CPU在任務等待IO的同時處理其他任務,那么CPU利用率就會提高。
一般來說,由于IO耗時遠大于CPU耗時,所以maxThreads根據公式計算出來的數量會遠大于CPU核數,這是正常的。
需要注意的是,這個值并不是越高越好。因為一旦線程過多,CPU就需要進行上下文切換,消耗部分CPU資源。因此,最好的辦法是利用上面的公式計算出一個基準值,然后進行壓力測試調整到合理的值。
一般來說,如果 的值maxThreads增大,但吞吐量沒有增加或減少,則可能表明已經達到瓶頸。
maxConnections
maxConnections指當線程池中的線程達到最大值并且全部在忙時,Connector中的隊列可以容納的最大連接數。
一般來說,我們要設定一個合理的值,不能讓它無限制地堆積。
因為Tomcat的處理能力肯定是有限的,到了一定程度就肯定處理不了了。所以,積累太多也是沒有用的。反而會造成內存堆積,最終導致內存溢出OOM。
一般來說,經驗值可以設置為與 maxThreads相同的大小。
這樣比較合理,因為隊列中的連接最多只需要等待線程處理一個任務,就不會等待太久,響應時間也不會太長。如果想縮短響應時間,可以調整maxConnections低于maxThreads,這樣可以減少一些響應時間。
但需要注意的是,如果降得太低,性能可能會嚴重下降,吞吐量也會降低。
acceptCount
acceptCount指當Container線程池達到最大數量并且沒有空閑線程且Connector隊列達到最大數量時,操作系統可以接受的最大連接數。
當隊列中的數量達到最大值時,所有傳入的請求都將被拒絕。默認值為100。這可以理解為操作系統的一種自我保護機制。如果積累的太多無法處理,那就直接拒絕,以保護自己的資源。
該參數的調優資料相對較少,但根據其含義,不建議該值大于maxConnections。
因為這個隊列中的連接需要等待。如果該值太大,則意味著會有很多連接沒有被處理。
連接越多,等待時間越長,響應時間越慢。如果想要較短的響應時間,您可能應該降低該值。
有的同學會問,既然有了acceptCount,為什么還需要呢maxConnections?這不是重復了嗎?其實在BIO中,這兩個值基本是一致的。
猜測是因為后來NIO,AIO等技術的出現操作系統可以接受更多的客戶端連接。
因此,操作系統可以先建立連接緩存,然后Connector就可以直接從操作系統獲取連接,這樣就不需要等待操作系統建立耗時的TCP連接,從而提高效率。
其它參數
除了以上三個參數之外,還有幾個非核心參數,但我認為還是有一定作用的。
- connectionTimeout:表示連接建立后等待超時時間。如果超過這個時間,則直接返回超時時間。
- minSpareThreads:表示最小存活線程數,即如果沒有請求,那么必須保持存活的最小線程數。該參數與是否存在突發流量有關。在突發流量的情況下,如果該值太低,瞬時響應時間會比較長。
總結
今天我們分享了Tomcat的核心組件,然后講解了Tomcat在處理請求時的三個核心參數以及調優經驗。
對于maxThreads參數來說,如果按照公式計算,我們需要獲取IO時間和CPU時間,但實際上這兩個值并不容易獲取。
所以一般來說,我們可以通過壓力測試得到一個比較合適的maxThreads。
對于該maxConnections參數,您可以設置與maxThreads 相同的值,然后根據具體情況進行調整。如果你想減少響應時間,可以稍微調低,否則可以調高。
對于該acceptCount參數,其調優邏輯與maxConnections 類似,可以類似maxConnections 進行設置,然后根據相應的時間要求進行微調。






