接口流量突增,如何做好性能優(yōu)化?
?大家好,我是樹哥!
對(duì)于提供接口服務(wù)的應(yīng)用來(lái)說(shuō),很多都是用 SpringBoot 默認(rèn)的 Servlet 容器 Tomcat。在一開始上線的時(shí)候,由于大多數(shù)流量較小,我們也并不會(huì)為 Tomcat 做專門的參數(shù)調(diào)整。但隨著流量越來(lái)越大,應(yīng)用的各項(xiàng)性能指標(biāo)越來(lái)越差,此時(shí)我們大多數(shù)都會(huì)選擇擴(kuò)容。
除了擴(kuò)容之外,我們還可以選擇對(duì) Tomcat 進(jìn)行性能調(diào)優(yōu),從而在不增加成本的情況下提升性能。如果面試官問(wèn)你,流量突增你們一般怎么做,你只會(huì)答擴(kuò)容可就太差勁了。
今天樹哥就跟大家簡(jiǎn)單分享下,如何對(duì) Tomcat 進(jìn)行簡(jiǎn)單地性能調(diào)優(yōu),從而提升應(yīng)用的性能!
組件架構(gòu)
要對(duì) Tomcat 進(jìn)行性能調(diào)優(yōu),我們需要先了解其組件架構(gòu)。Tomcat 的組件架構(gòu)如下圖所示:
Tomcat 組件結(jié)構(gòu)示意圖
從上圖可以看到,Tomcat 將其業(yè)務(wù)抽象成了 Server、Service、Connector、Container 等等組件,每個(gè)組件都有不同的作用。
- Server 組件。Server 組件是 Tomcat 最外層的組件,該組件是 Tomcat 實(shí)例本身的抽象,代表著 Tomcat 自身。一個(gè) Server 組件可以有一個(gè)或多個(gè) Service 組件。
- Service 組件。Service 組件是 Tomcat 中一組提供服務(wù)、處理請(qǐng)求的組件,一個(gè) Service 組件可以有多個(gè) Connector 連接器和一個(gè) Container,有多個(gè) Connector 表示其可以同時(shí)使用多種協(xié)議接收用戶請(qǐng)求。
- Connector 組件。Connector 負(fù)責(zé)處理客戶端的連接,它提供各種服務(wù)協(xié)議支持,包括:BIO、NIO、AIO 等等。其存在的價(jià)值在于,為 Container 容器屏蔽了多協(xié)議的復(fù)雜性,統(tǒng)一了 Container 容器的處理標(biāo)準(zhǔn)。
- Container 組件。Container 組件是負(fù)責(zé)具體業(yè)務(wù)邏輯處理的容器,當(dāng) Connector 組件與客戶端建立連接后,便會(huì)將請(qǐng)求轉(zhuǎn)發(fā)給 Container 組件的 Engine 組件處理。
到這里,Tomcat 的核心組件基本上講完了。實(shí)際上 Container 組件里還細(xì)分了很多組件,其實(shí)對(duì)業(yè)務(wù)的抽象,感興趣的可以繼續(xù)看看。
- Engine 組件。Engine 組件表示可運(yùn)行的 Servlet 實(shí)例,包含了 Servlet 容器的核心功能,其可以有一個(gè)或多個(gè)虛擬主機(jī)(Host)。其主要功能是將請(qǐng)求委托給合適的虛擬主機(jī)處理,即根據(jù) URL 路徑的配置匹配到合適的虛擬主機(jī)處理。
- Host 組件。Host 組件負(fù)責(zé)運(yùn)行多個(gè)應(yīng)用,其負(fù)責(zé)安裝這些應(yīng)用,其主要作用是解析 web.xml 文件,并將其匹配到對(duì)應(yīng)的 Context 組件。
- Context 組件。Context 組件代表具體的 Web 應(yīng)用程序本身,其最重要的功能就是管理里面的 Servlet 實(shí)例。一個(gè) Context 可以有一個(gè)或者多個(gè) Servlet 實(shí)例。
- Wrapper 組件。一個(gè) Wrapper 組件代表一個(gè) Servlet,它負(fù)責(zé)管理一個(gè) Servlet,包括 Servlet 的裝載、初始化、執(zhí)行以及資源回收。Wrapper 是最底層的容器。
可以看到,Host 是虛擬主機(jī)的抽象,Context 是應(yīng)用程序的抽象,Wrapper 是 Servlet 的抽象,而 Engine 則是處理層的抽象。
核心參數(shù)
在了解核心參數(shù)之前,我們我們需要大致了解一下 Tomcat 對(duì)于請(qǐng)求的處理流程。Tomcat 對(duì)請(qǐng)求的處理流程如下所示:
- 首先,客戶端向 Tomcat 服務(wù)器發(fā)起請(qǐng)求,Connector 組件監(jiān)聽到請(qǐng)求,于是與客戶端建立起連接。
- 接著,Connector 將請(qǐng)求封裝后轉(zhuǎn)發(fā)給 Engine 組件處理。
- 最后,Engine 組件處理完之后將結(jié)果返回給 Connector,Connector 組件再將結(jié)果返回給客戶端。
上述過(guò)程可以用如下示意圖來(lái)表示:
Tomcat 核心參數(shù)示意圖
在上面的示意圖中有三個(gè)非常關(guān)鍵的核心參數(shù),這幾個(gè)關(guān)鍵的參數(shù)也是性能調(diào)優(yōu)的關(guān)鍵,它們分別是:
- acceptCount:當(dāng) Container 線程池達(dá)到最大數(shù)量且沒(méi)有空閑線程,同時(shí) Connector 隊(duì)列達(dá)到最大數(shù)量時(shí),操作系統(tǒng)最多能接受的連接數(shù)。
- maxConnections:當(dāng) Container 線程池達(dá)到最大數(shù)量且沒(méi)有空閑線程時(shí),Connector 的隊(duì)列能接收的最大線程數(shù)。
- maxThreads:Container 線程池的處理線程的最大數(shù)量。
從上面三個(gè)參數(shù)的含義我們可以知道如下幾點(diǎn)結(jié)論:
- 客戶端并不是直接與 Tomcat 的 Connector 組件建立聯(lián)系的,而是先與操作系統(tǒng)建立,然后再移交給 Connector 的。這點(diǎn)很重要,不然你就無(wú)法理解 acceptCount 這個(gè)參數(shù)。
- 不僅僅 Connector 組件中有隊(duì)列,操作系統(tǒng)中也有隊(duì)列來(lái)臨時(shí)存儲(chǔ)與客戶端的連接,這也是很關(guān)鍵的點(diǎn)。
- 我們所說(shuō)的線程池,指的是 Container 這個(gè)容器里的線程池。
明白這三個(gè)核心參數(shù)的含義是非常重要的,不然沒(méi)有辦法進(jìn)行后續(xù)的性能調(diào)優(yōu)工作。
maxThreads
我們知道 maxThreads 指的是請(qǐng)求處理線程的最大數(shù)量,在 Tomcat7 和 Tomcat8 中都是默認(rèn) 200 個(gè)。
對(duì)于這個(gè)參數(shù)的設(shè)置,需要根據(jù)任務(wù)的執(zhí)行內(nèi)容去調(diào)整,一般來(lái)說(shuō)計(jì)算公式為:最大線程數(shù) = ((IO時(shí)間 + CPU時(shí)間)/CPU時(shí)間) * CPU 核數(shù)。這個(gè)公式的思路其實(shí)很簡(jiǎn)單,就是最大化利用 CPU 的資源。一個(gè)任務(wù)的耗時(shí)分為 IO 耗時(shí)和 CPU 耗時(shí),基本上 IO 耗時(shí)是最多的,這時(shí)候 CPU 是沒(méi)事干的。
因此如果可以讓 CPU 在任務(wù)等待 IO 的時(shí)候處理其他任務(wù),那么 CPU 利用率不就上來(lái)了么。一般來(lái)說(shuō),由于 IO 耗時(shí)遠(yuǎn)大于 CPU 耗時(shí),因此根據(jù)公式計(jì)算出來(lái)的 maxThreads 數(shù)都會(huì)遠(yuǎn)大于 CPU 核數(shù),這是很正常的。
要注意的是,這個(gè)數(shù)值也不是越高越好。因?yàn)橐坏┚€程數(shù)太多了,CPU 需要進(jìn)行上下文切換,這就消耗了一部分 CPU 資源。因此最好的辦法是用上述公式去計(jì)算一個(gè)基準(zhǔn)值,隨后再進(jìn)行壓力測(cè)試,去調(diào)整到一個(gè)合理的值。
一般來(lái)說(shuō),如果調(diào)高了 maxThreads 的值,但是吞吐量沒(méi)有提升或者下降的話,那么表明可能到達(dá)了了瓶頸了。
maxConnections
maxConnections 指的是當(dāng)線程池的線程達(dá)到最大值,并且都在忙的時(shí)候,Connector 中的隊(duì)列最多能容納多少個(gè)連接。一般來(lái)說(shuō),我們都要設(shè)置一個(gè)合理的數(shù)值,不能讓其無(wú)限制堆積。
因?yàn)?Tomcat 的處理能力肯定是有限的,到達(dá)一定程度肯定就處理不過(guò)來(lái)了,因此你堆積太多了也沒(méi)啥用,反而會(huì)造成內(nèi)存堆積,最終導(dǎo)致內(nèi)存溢出 OOM 的發(fā)生。
一般來(lái)說(shuō),一個(gè)經(jīng)驗(yàn)值是可以設(shè)置成為 maxThreads 同樣的大小。 我想這樣也是比較合理的,因?yàn)樵陉?duì)列中的連接最多只需要等待線程處理一個(gè)任務(wù)的時(shí)間即可,不會(huì)等待太久,響應(yīng)時(shí)間也不會(huì)太長(zhǎng)。
如果你想縮短響應(yīng)時(shí)間,那么可以將 maxConnections 調(diào)低于 maxThreads 一些,這樣可以降低一些響應(yīng)時(shí)間。但要注意的是,如果降得太低的話,可能就會(huì)嚴(yán)重降低性能,降低吞吐量。
acceptCount
acceptCount 指的是當(dāng) Container 線程池達(dá)到最大數(shù)量且沒(méi)有空閑線程,同時(shí) Connector 隊(duì)列達(dá)到最大數(shù)量時(shí),操作系統(tǒng)最多能接受的連接數(shù)。 當(dāng)
隊(duì)列中的個(gè)數(shù)達(dá)到最大值后,進(jìn)來(lái)的請(qǐng)求一律被拒絕,默認(rèn)值是 100。這可以理解成是操作系統(tǒng)的一種自我保護(hù)機(jī)制吧,堆積太多無(wú)法處理,那就直接拒絕掉,保護(hù)自身資源。
這個(gè)參數(shù)的調(diào)優(yōu)資料比較少,但根據(jù)其含義,這個(gè)值不建議比 maxConnections 大。 因?yàn)樵谶@個(gè)隊(duì)列中的連接,是需要等待的。如果數(shù)值太大,就說(shuō)明會(huì)有很多連接沒(méi)有被處理。
連接越多,那么其等待的時(shí)間就越長(zhǎng),其響應(yīng)時(shí)間就越慢。如果你想響應(yīng)時(shí)間短一些,或許應(yīng)該調(diào)低一下這個(gè)值。
有同學(xué)會(huì)疑惑,為啥有了 maxConnections 了還要有 acceptCount 呢?這不是重復(fù)了么?其實(shí)在 BIO 的時(shí)代,這兩個(gè)數(shù)值基本都是相同的。我猜是因?yàn)楹竺娉霈F(xiàn)了 NIO、AIO 等技術(shù),操作系統(tǒng)可以接受更多的客戶端連接了。
于是就可以先讓操作系統(tǒng)先建立連接緩存著,隨后 Connnector 直接從操作系統(tǒng)處獲取連接即可,這樣就不需要等待操作系統(tǒng)進(jìn)行耗時(shí)的 TCP 連接了,從而提高了效率。
除了上面這三個(gè)參數(shù)之外,還有幾個(gè)非核心參數(shù),但我覺得還是有些作用的。
- connectionTimeout 參數(shù),表示建立連接后的等待超時(shí)時(shí)間,如果超過(guò)這個(gè)時(shí)間,那么就會(huì)直接返回超時(shí)。
- minSpareThreads 參數(shù),表示最小存活線程數(shù),也就是如果沒(méi)有請(qǐng)求了,那么最低要保持幾個(gè)線程存活。這個(gè)參數(shù)與是否有突發(fā)流程相關(guān)聯(lián),在有突發(fā)流量的情況下,如果這個(gè)數(shù)值太低,那么就會(huì)導(dǎo)致瞬時(shí)的響應(yīng)時(shí)間比較長(zhǎng)。
總結(jié)
今天我們分享了 Tomcat 的核心組件,接著講解了 Tomcat 處理請(qǐng)求過(guò)程時(shí)的 3 個(gè)核心參數(shù)及其調(diào)優(yōu)經(jīng)驗(yàn)。
對(duì)于 maxThreads 參數(shù)而言,如果按照公式計(jì)算的話,我們需要獲取 IO 時(shí)間和 CPU 時(shí)間,但實(shí)際上這兩個(gè)值并不是很好獲取。所以一般情況下,我們可以通過(guò)壓測(cè)的方式來(lái)獲得一個(gè)比較合適的 maxThreads。
對(duì)于 maxConnections 參數(shù)而言,可以設(shè)置一個(gè)與 maxThreads 相同的值,再根據(jù)具體情況進(jìn)行調(diào)整。如果想降低響應(yīng)時(shí)間,那么可以稍微調(diào)低一些,否則可以調(diào)高一些。
對(duì)于 acceptCount 參數(shù)而言,其調(diào)優(yōu)邏輯與 maxConnections 類似,可以設(shè)置與 maxConnections 相似,再根據(jù)對(duì)相應(yīng)時(shí)間的要求,做一個(gè)微調(diào)。