如何對(duì)Tomcat進(jìn)行性能優(yōu)化?
對(duì)于提供接口服務(wù)的應(yīng)用程序來說,很多都使用SpringBoot默認(rèn)的Servlet容器Tomcat。
上線之初,由于大部分流量較小,我們不會(huì)對(duì)Tomcat進(jìn)行特殊的參數(shù)調(diào)整。
然而,隨著流量的增加,應(yīng)用的性能指標(biāo)變得越來越差。這個(gè)時(shí)候我們大多數(shù)人都會(huì)選擇擴(kuò)容。除了擴(kuò)容之外,我們還可以選擇對(duì)Tomcat進(jìn)行性能調(diào)優(yōu),在不增加成本的情況下提升性能。
今天我們將分享如何對(duì)Tomcat進(jìn)行簡單的性能調(diào)優(yōu),以提高應(yīng)用程序的性能。
tomcat的架構(gòu)

從上圖可以看出,Tomcat將其業(yè)務(wù)抽象為Server, Service, Connector, ContAIner等組件,每個(gè)組件都有不同的作用。
Server組件是Tomcat的最外層組件,它是Tomcat實(shí)例本身的抽象,代表Tomcat本身。一個(gè)服務(wù)器組件可以有一個(gè)或多個(gè)服務(wù)組件。
Service組件是Tomcat中提供服務(wù)和處理請(qǐng)求的一組組件。一個(gè)服務(wù)組件可以有多個(gè)連接器和一個(gè)容器。多個(gè)Connector表示它可以使用多種協(xié)議同時(shí)接收用戶請(qǐng)求。
連接器負(fù)責(zé)處理客戶端連接,它提供對(duì)各種服務(wù)協(xié)議的支持,包括BIO、NIO、AIO等,它存在的價(jià)值在于屏蔽多協(xié)議Container的復(fù)雜性,統(tǒng)一Container的處理標(biāo)準(zhǔn)。
Container組件是負(fù)責(zé)具體業(yè)務(wù)邏輯處理的容器。當(dāng)Connector組件與客戶端建立連接時(shí),它會(huì)將請(qǐng)求轉(zhuǎn)發(fā)給Container組件的Engine組件進(jìn)行處理。
至此,Tomcat的核心組件就基本完成了。其實(shí)Container組件里面還有很多細(xì)分的組件。如果你對(duì)業(yè)務(wù)的抽象感興趣的話,可以繼續(xù)看下去。
- Engine組件代表一個(gè)可運(yùn)行的Servlet實(shí)例,包含Servlet容器的核心功能,可以擁有一個(gè)或多個(gè)虛擬主機(jī)(Host)。其主要作用是將請(qǐng)求委托給合適的虛擬主機(jī)進(jìn)行處理,即根據(jù)URL路徑的配置來匹配合適的虛擬主機(jī)進(jìn)行處理。
- Host組件負(fù)責(zé)運(yùn)行多個(gè)應(yīng)用程序,并且它負(fù)責(zé)安裝這些應(yīng)用程序。它的主要功能是解析web.xml文件并匹配到相應(yīng)的Context組件。
- Context組件代表了具體的Web應(yīng)用程序本身,它最重要的功能就是管理里面的Servlet實(shí)例。一個(gè) Context 可以有一個(gè)或多個(gè) Servlet 實(shí)例。
- 一個(gè)WrApper組件代表一個(gè)Servlet,它負(fù)責(zé)管理一個(gè)Servlet,包括Servlet的加載、初始化、執(zhí)行和資源回收等。包裝器是最低級(jí)別的容器。
可以看出,Host是虛擬主機(jī)的抽象,Context是應(yīng)用程序的抽象,Wrapper是Servlet的抽象,Engine是處理層的抽象
核心參數(shù)
在了解核心參數(shù)之前,我們需要對(duì)Tomcat對(duì)于請(qǐng)求的處理流程有一個(gè)大概的了解。
Tomcat對(duì)請(qǐng)求的處理流程如下。

在上面的示意圖中,有三個(gè)非常關(guān)鍵的核心參數(shù),這也是性能調(diào)優(yōu)的關(guān)鍵。
- acceptCount:當(dāng)Container線程池達(dá)到最大數(shù)量且沒有空閑線程,且Connector隊(duì)列達(dá)到最大數(shù)量時(shí),操作系統(tǒng)可以接受的最大連接數(shù)。
- maxConnections:當(dāng)Container線程池達(dá)到最大數(shù)量并且沒有空閑線程時(shí),Connector的隊(duì)列可以接收的最大線程數(shù)量。
- maxThreads:Container線程池中最大處理線程數(shù)。
從以上三個(gè)參數(shù)的含義,我們可以得知以下結(jié)論。
客戶端并不直接與Tomcat的Connector組件建立聯(lián)系,而是先與操作系統(tǒng)建立聯(lián)系,然后交給Connector。
這一點(diǎn)非常重要,否則你將無法理解該acceptCount參數(shù)。
不僅Connector組件中有一個(gè)隊(duì)列,操作系統(tǒng)中也有一個(gè)隊(duì)列來臨時(shí)存儲(chǔ)與客戶端的連接,這也是一個(gè)關(guān)鍵點(diǎn)。我們所說的線程池是指Container容器中的線程池。
理解這三個(gè)核心參數(shù)的含義非常重要,否則就沒有辦法進(jìn)行后續(xù)的性能調(diào)優(yōu)工作。
maxThreads
我們知道指的maxThreads是最大請(qǐng)求處理線程數(shù),Tomcat7和Tomcat8都是默認(rèn)的200。
該參數(shù)的設(shè)置需要根據(jù)任務(wù)的執(zhí)行內(nèi)容進(jìn)行調(diào)整。一般來說,計(jì)算公式為:最大線程數(shù) = ((IO time + CPU time)/CPU time) * CPU核心數(shù)。
這個(gè)公式的思想其實(shí)很簡單,就是最大化的利用CPU資源。
任務(wù)的時(shí)間消耗分為IO時(shí)間消耗和CPU時(shí)間消耗。基本上IO時(shí)間消耗是最多的,此時(shí)CPU無事可做。
所以如果可以讓CPU在任務(wù)等待IO的同時(shí)處理其他任務(wù),那么CPU利用率就會(huì)提高。
一般來說,由于IO耗時(shí)遠(yuǎn)大于CPU耗時(shí),所以maxThreads根據(jù)公式計(jì)算出來的數(shù)量會(huì)遠(yuǎn)大于CPU核數(shù),這是正常的。
需要注意的是,這個(gè)值并不是越高越好。因?yàn)橐坏┚€程過多,CPU就需要進(jìn)行上下文切換,消耗部分CPU資源。因此,最好的辦法是利用上面的公式計(jì)算出一個(gè)基準(zhǔn)值,然后進(jìn)行壓力測(cè)試調(diào)整到合理的值。
一般來說,如果 的值maxThreads增大,但吞吐量沒有增加或減少,則可能表明已經(jīng)達(dá)到瓶頸。
maxConnections
maxConnections指當(dāng)線程池中的線程達(dá)到最大值并且全部在忙時(shí),Connector中的隊(duì)列可以容納的最大連接數(shù)。
一般來說,我們要設(shè)定一個(gè)合理的值,不能讓它無限制地堆積。
因?yàn)門omcat的處理能力肯定是有限的,到了一定程度就肯定處理不了了。所以,積累太多也是沒有用的。反而會(huì)造成內(nèi)存堆積,最終導(dǎo)致內(nèi)存溢出OOM。
一般來說,經(jīng)驗(yàn)值可以設(shè)置為與 maxThreads相同的大小。
這樣比較合理,因?yàn)殛?duì)列中的連接最多只需要等待線程處理一個(gè)任務(wù),就不會(huì)等待太久,響應(yīng)時(shí)間也不會(huì)太長。如果想縮短響應(yīng)時(shí)間,可以調(diào)整maxConnections低于maxThreads,這樣可以減少一些響應(yīng)時(shí)間。
但需要注意的是,如果降得太低,性能可能會(huì)嚴(yán)重下降,吞吐量也會(huì)降低。
acceptCount
acceptCount指當(dāng)Container線程池達(dá)到最大數(shù)量并且沒有空閑線程且Connector隊(duì)列達(dá)到最大數(shù)量時(shí),操作系統(tǒng)可以接受的最大連接數(shù)。
當(dāng)隊(duì)列中的數(shù)量達(dá)到最大值時(shí),所有傳入的請(qǐng)求都將被拒絕。默認(rèn)值為100。這可以理解為操作系統(tǒng)的一種自我保護(hù)機(jī)制。如果積累的太多無法處理,那就直接拒絕,以保護(hù)自己的資源。
該參數(shù)的調(diào)優(yōu)資料相對(duì)較少,但根據(jù)其含義,不建議該值大于maxConnections。
因?yàn)檫@個(gè)隊(duì)列中的連接需要等待。如果該值太大,則意味著會(huì)有很多連接沒有被處理。
連接越多,等待時(shí)間越長,響應(yīng)時(shí)間越慢。如果想要較短的響應(yīng)時(shí)間,您可能應(yīng)該降低該值。
有的同學(xué)會(huì)問,既然有了acceptCount,為什么還需要呢maxConnections?這不是重復(fù)了嗎?其實(shí)在BIO中,這兩個(gè)值基本是一致的。
猜測(cè)是因?yàn)楹髞鞱IO,AIO等技術(shù)的出現(xiàn)操作系統(tǒng)可以接受更多的客戶端連接。
因此,操作系統(tǒng)可以先建立連接緩存,然后Connector就可以直接從操作系統(tǒng)獲取連接,這樣就不需要等待操作系統(tǒng)建立耗時(shí)的TCP連接,從而提高效率。
其它參數(shù)
除了以上三個(gè)參數(shù)之外,還有幾個(gè)非核心參數(shù),但我認(rèn)為還是有一定作用的。
- connectionTimeout:表示連接建立后等待超時(shí)時(shí)間。如果超過這個(gè)時(shí)間,則直接返回超時(shí)時(shí)間。
- minSpareThreads:表示最小存活線程數(shù),即如果沒有請(qǐng)求,那么必須保持存活的最小線程數(shù)。該參數(shù)與是否存在突發(fā)流量有關(guān)。在突發(fā)流量的情況下,如果該值太低,瞬時(shí)響應(yīng)時(shí)間會(huì)比較長。
總結(jié)
今天我們分享了Tomcat的核心組件,然后講解了Tomcat在處理請(qǐng)求時(shí)的三個(gè)核心參數(shù)以及調(diào)優(yōu)經(jīng)驗(yàn)。
對(duì)于maxThreads參數(shù)來說,如果按照公式計(jì)算,我們需要獲取IO時(shí)間和CPU時(shí)間,但實(shí)際上這兩個(gè)值并不容易獲取。
所以一般來說,我們可以通過壓力測(cè)試得到一個(gè)比較合適的maxThreads。
對(duì)于該maxConnections參數(shù),您可以設(shè)置與maxThreads 相同的值,然后根據(jù)具體情況進(jìn)行調(diào)整。如果你想減少響應(yīng)時(shí)間,可以稍微調(diào)低,否則可以調(diào)高。
對(duì)于該acceptCount參數(shù),其調(diào)優(yōu)邏輯與maxConnections 類似,可以類似maxConnections 進(jìn)行設(shè)置,然后根據(jù)相應(yīng)的時(shí)間要求進(jìn)行微調(diào)。