為什么你的服務(wù)會(huì)變慢?
你開發(fā)了一個(gè)服務(wù),調(diào)用它,它做了一些事情并返回結(jié)果。那么,它需要花多長(zhǎng)時(shí)間?為什么有時(shí)候它花的時(shí)間比用戶期望的要長(zhǎng)?在這篇文章中,我將從最基礎(chǔ)的講起,然后逐步介紹一些標(biāo)準(zhǔn)的術(shù)語,同時(shí)著重強(qiáng)調(diào)一些需要知道的關(guān)鍵點(diǎn)。
首先,我們需要一種方式來度量時(shí)長(zhǎng),還需要理解兩個(gè)完全不同的度量角度。從調(diào)用服務(wù)的外部用戶角度來看,我們需要度量響應(yīng)時(shí)間。從服務(wù)處理請(qǐng)求的角度來看,我們需要度量服務(wù)時(shí)間。這就引出了第一個(gè)關(guān)鍵點(diǎn),人們常常分不清一些指標(biāo)。
對(duì)于用戶來說是響應(yīng)時(shí)間(Response Time),對(duì)于服務(wù)來說是服務(wù)時(shí)間(Service Time)。
在真實(shí)世界里,每一個(gè)處理過程都包含了很多步驟,每個(gè)步驟都需要占用一些時(shí)間。步驟占用的時(shí)間叫作駐留時(shí)間(Residence Time),駐留時(shí)間由等待時(shí)間(Wait Time)和服務(wù)時(shí)間組成。以用戶登錄 App 為例,一個(gè)用戶登錄手機(jī) App,App 會(huì)調(diào)用 Web 服務(wù)進(jìn)行用戶認(rèn)證。為什么有時(shí)候會(huì)很慢?按理說,每一次手機(jī)上生成請(qǐng)求的時(shí)間、將請(qǐng)求傳輸給 Web 服務(wù)的時(shí)間、查詢用戶的時(shí)間、返回結(jié)果并顯示下一個(gè)屏幕的時(shí)間應(yīng)該是一樣的。造成響應(yīng)時(shí)間長(zhǎng)短不一的是排隊(duì)時(shí)間,也就是等待正在處理其他請(qǐng)求的資源。從手機(jī)到認(rèn)證服務(wù)器之間的網(wǎng)絡(luò)傳輸需要經(jīng)過很多跳,每一跳前面都有等待被發(fā)送的數(shù)據(jù)包。如果隊(duì)列是空的或者隊(duì)列很短,那么響應(yīng)速度就很快,如果隊(duì)列很長(zhǎng),響應(yīng)就很慢。當(dāng)請(qǐng)求達(dá)到服務(wù)器時(shí),也需要排隊(duì)等待 CPU 處理。如果需要查詢數(shù)據(jù)庫,還需要排到另一個(gè)隊(duì)列里。
排隊(duì)等待是導(dǎo)致響應(yīng)時(shí)間增加的主要原因。
監(jiān)控工具會(huì)提供一個(gè)叫作吞吐量(Throughput)的指標(biāo),用來度量處理頻度。在某些情況下,我們也會(huì)得到一個(gè)叫作到達(dá)率(Arrival Rate)的指標(biāo),用于度量到達(dá)服務(wù)器的請(qǐng)求的速率。在理想情況下,比如一個(gè)具有穩(wěn)定工作負(fù)載狀態(tài)的 Web 服務(wù),一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)響應(yīng),那么吞吐量和到達(dá)率是一樣的。不過,重試和錯(cuò)誤會(huì)導(dǎo)致到達(dá)率增加,但吞吐量不會(huì)增加。對(duì)于快速變化的工作負(fù)載或者需要長(zhǎng)時(shí)間處理的請(qǐng)求(比如批次作業(yè)),會(huì)出現(xiàn)到達(dá)率和吞吐量之間的不均衡,并產(chǎn)生更為復(fù)雜的請(qǐng)求模式。
吞吐量是指已經(jīng)成功處理完的請(qǐng)求數(shù)量,它跟達(dá)到率是不一樣的。
我們可以通過一些跟蹤系統(tǒng)(比如 Zipkin 或 AWS X-Ray)來跟蹤單個(gè)請(qǐng)求流,不過我們這里討論的是大量請(qǐng)求以及它們之間的交互關(guān)系。我們通過固定的時(shí)間間隔來度量均值,時(shí)間間隔可以是秒、分鐘、小時(shí)或天。計(jì)算均值需要足夠多的數(shù)據(jù),一般來說每個(gè)均值至少需要 20 個(gè)數(shù)據(jù)點(diǎn)。
如果請(qǐng)求不是很頻繁,請(qǐng)選擇一個(gè)至少包含 20 個(gè)請(qǐng)求的時(shí)間間隔,這樣才有可能得到比較有用的信息。
如果選擇的時(shí)間間隔太大,會(huì)導(dǎo)致工作負(fù)載的變化被隱藏掉。例如,對(duì)于視頻會(huì)議系統(tǒng)來說,大部分會(huì)話會(huì)在一個(gè)小時(shí)的頭一分鐘左右啟動(dòng),并且很容易在這些時(shí)間段達(dá)到峰值,讓系統(tǒng)發(fā)生過載,如果時(shí)間間隔是小時(shí),這些信息就會(huì)丟失掉。所以,對(duì)于這種情況,時(shí)間間隔設(shè)為秒更為恰當(dāng)。
對(duì)于變化快的工作負(fù)載,可以使用秒級(jí)的均值。
監(jiān)控工具各種各樣,但一般很少會(huì)直接告訴我們等待隊(duì)列有多長(zhǎng)或有多少并發(fā)度可用來處理隊(duì)列。大多數(shù)網(wǎng)絡(luò)每次只傳輸一個(gè)數(shù)據(jù)包,但 CPU 的每個(gè)核心或 vCPU 可以并行處理隊(duì)列里的任務(wù)。數(shù)據(jù)庫通常有一個(gè)固定的最大連接數(shù),用來限制并發(fā)度。
對(duì)于處理請(qǐng)求的每一個(gè)步驟,可以記錄或估計(jì)用于處理請(qǐng)求的并發(fā)度。
如果系統(tǒng)運(yùn)行穩(wěn)定,有穩(wěn)定的平均吞吐量和響應(yīng)時(shí)間,那就很容易估算等待隊(duì)列的長(zhǎng)度,只需要將吞吐量和駐留時(shí)間相乘即可。這就是所謂的利特爾法則法則(Little's Law)。這個(gè)法則很簡(jiǎn)單,監(jiān)控工具經(jīng)常用它來估算隊(duì)列長(zhǎng)度,但它只對(duì)具有穩(wěn)定均值的系統(tǒng)有效。
根據(jù)利特爾法則,平均隊(duì)列長(zhǎng)度 = 平均吞吐量 * 平均駐留時(shí)間。
為了更好地理解這個(gè)法則的原理,我們需要知道請(qǐng)求是如何到達(dá)服務(wù)器以及請(qǐng)求之間的間隔是怎樣的。如果我們通過循環(huán)進(jìn)行簡(jiǎn)單的性能測(cè)試,請(qǐng)求之間的間隔是固定的,那么利特爾法則就無效,因?yàn)檫@樣出現(xiàn)的隊(duì)列很短,而且這樣的測(cè)試不真實(shí)。我們通常會(huì)進(jìn)行這樣的測(cè)試,以為很完美,但是在將服務(wù)部署到生產(chǎn)環(huán)境之后,眼睜睜地看著它越跑越慢,吞吐量越來越低。
這種速率固定的循環(huán)測(cè)試不會(huì)有隊(duì)列出現(xiàn),它們只是在模擬傳送帶。
在真實(shí)的網(wǎng)絡(luò)世界中,用戶都是獨(dú)立的,他們發(fā)送自己的請(qǐng)求,不同用戶發(fā)送的請(qǐng)求之間的間隔是隨機(jī)的。所以,在測(cè)試時(shí),我們需要使用可以生成具有隨機(jī)等待時(shí)間的請(qǐng)求的生成器。大多數(shù)系統(tǒng)會(huì)使用隨機(jī)分布,雖然比模擬傳送帶要好,但也是不對(duì)的。要模擬真實(shí)的網(wǎng)絡(luò)流量,并讓利特爾法則生效,我們需要使用負(fù)指數(shù)分布(Negative Exponential Distribution)。Neil Cunther 博士在這篇文章中解釋了什么是負(fù)指數(shù)分布。
http://perfdynamics.blogspot.com/2012/05/load-testing-with-uniform-vs.html
要生成更加真實(shí)的隊(duì)列,需要使用恰當(dāng)?shù)碾S機(jī)時(shí)間算法。
但問題是,真實(shí)的網(wǎng)絡(luò)流量并不是隨機(jī)分布的,而是帶有爆發(fā)性質(zhì)的。想象一下,當(dāng)一個(gè)用戶打開一個(gè)手機(jī) App,它不會(huì)只發(fā)出一個(gè)請(qǐng)求,而是很多個(gè)。在網(wǎng)絡(luò)購物搶購活動(dòng)中,會(huì)有很多用戶同時(shí)打開 App,這會(huì)導(dǎo)致流量爆發(fā)。這種分布形態(tài)叫作帕累托或雙曲線。另外,當(dāng)網(wǎng)絡(luò)經(jīng)過重新配置,流量會(huì)被延遲,就會(huì)出現(xiàn)隊(duì)列,而隊(duì)列會(huì)給下游系統(tǒng)帶來閃電式的沖擊。Jim Brady 和 Neil Gunther 寫了一些腳本,演示如何配置測(cè)試工具,從而獲得更加真實(shí)的流量。Jim Brady 還寫了一篇關(guān)于如何知道負(fù)載測(cè)試好壞的論文。
- https://github.com/DrQz/web-generator-toolkit
- https://arxiv.org/abs/1809.10663
相比常用測(cè)試工具默認(rèn)生成的流量負(fù)載,真實(shí)世界的流量負(fù)載更具爆發(fā)性,會(huì)導(dǎo)致更長(zhǎng)的等待隊(duì)列和響應(yīng)時(shí)間。
等待隊(duì)列和響應(yīng)時(shí)間應(yīng)該是變化的,而且即使是在使用率很低的時(shí)候也會(huì)出現(xiàn)一些很慢的請(qǐng)求處理速度。那么,當(dāng)處理步驟中的某一步開始變慢時(shí)會(huì)怎樣?當(dāng)使用率增加,一些處理步驟沒有足夠的可用資源(比如網(wǎng)絡(luò)傳輸),那么請(qǐng)求相互爭(zhēng)奪資源的情況就會(huì)增加,駐留時(shí)間也會(huì)增加。一般來說,當(dāng)使用率達(dá)到 50% 到 70% 時(shí),網(wǎng)絡(luò)就會(huì)逐漸變慢。
將網(wǎng)絡(luò)使用率保持在 50% 以下可用獲得更好的延時(shí)。
對(duì)于并行度高的 CPU,在使用率較高的情況下,速度會(huì)變得更慢,影響也更大,大到令你吃驚。如果你將最后一個(gè)可用的 CPU 看作爭(zhēng)用點(diǎn),那么這就很直觀了。例如,如果有 16 個(gè) vCPU,最后可用的 CPU 具有 6.25% 的處理能力,那么使用率就是 93.75%。對(duì)于具有 100 個(gè) vCPU 的系統(tǒng),它的使用率約為 99%。在穩(wěn)定狀態(tài)下,公式 R=S/(1-U^N) 可用來近似估算隨機(jī)到達(dá)服務(wù)器的請(qǐng)求的行為。
在多處理器系統(tǒng)中,隨著使用率的增加,平均駐留時(shí)間的膨脹會(huì)減少,但強(qiáng)度卻增加了。
使用率使用比例,而不是百分比,并將其作為處理器核數(shù)的冪底數(shù)。用 1 減掉使用率的 N 次冪,再用平均服務(wù)時(shí)間除以結(jié)果,就可以估算出平均駐留時(shí)間。如果使用率很低,平均駐留時(shí)間就會(huì)很接近平均服務(wù)時(shí)間。如果一個(gè)網(wǎng)絡(luò)的 N=1 并且使用率為 70%,那么用平均服務(wù)時(shí)間除以 0.3,得到的平均駐留時(shí)間就是低使用率時(shí)的三倍。
通常情況下,我們需要將平均駐留時(shí)間保持在 2 到 3 倍以下,這樣才能獲得更短的用戶響應(yīng)時(shí)間。
對(duì)于一個(gè)有 16 個(gè) vCPU 并且使用率為 95% 的系統(tǒng),0.95^16=0.44,再用平均服務(wù)時(shí)間除以 0.56,就會(huì)得到兩倍的平均駐留時(shí)間。如果使用率為 98%,那么 0.98^16=0.72,再用平均服務(wù)時(shí)間除以 0.28,平均駐留時(shí)間就會(huì)變慢,變得不可接受,而此時(shí)使用率僅增加了 3%。
當(dāng)多處理器系統(tǒng)的使用率很高時(shí),一個(gè)很小的負(fù)載變化就會(huì)產(chǎn)生很大的影響,這是多處理器系統(tǒng)的一個(gè)問題。
Unix/Linux 系統(tǒng)有一個(gè)指標(biāo)叫作負(fù)載平均(Load Average),人們通常對(duì)它了解得不夠透徹,它存在一些問題。Unix 系統(tǒng)(包括 Solaris、AIX、HPUX)會(huì)記錄運(yùn)行中的和等待 CPU 的線程數(shù),Linux 還會(huì)記錄等待 IO 阻塞的線程數(shù),然后還使用了三種時(shí)間衰減值,分別是 1 分鐘、5 分鐘和 15 分鐘。首先我們需要知道的是,這個(gè)指標(biāo)可以追溯到 60 年代的單核 CPU 時(shí)代,所以我通常會(huì)將負(fù)載平均值除以 vCPU 的數(shù)量,從而得到具有可比性的值。其次,這個(gè)指標(biāo)與其他指標(biāo)不一樣,它沒有使用固定的時(shí)間間隔,所以它們不屬于同一種平均值。第三,這個(gè)指標(biāo)在 Linux 上的實(shí)現(xiàn)已經(jīng)成了一個(gè) bug,被制度化成一個(gè)系統(tǒng)特性,其結(jié)果也被夸大了。
負(fù)載平均這個(gè)指標(biāo)不度量負(fù)載,也不是均值,所以最好把它忽略掉。
如果一個(gè)系統(tǒng)超載,請(qǐng)求達(dá)到的速度超過了處理能力,使用率就達(dá)到了 100%,那么上面的那個(gè)公式的除數(shù)就是 0,這樣會(huì)導(dǎo)致駐留時(shí)間無窮大。在實(shí)際當(dāng)中,這種情況會(huì)更加糟糕,因?yàn)楫?dāng)系統(tǒng)變慢時(shí),上游的用戶會(huì)發(fā)送重試請(qǐng)求,這樣會(huì)加大系統(tǒng)的負(fù)載,出現(xiàn)“重試風(fēng)暴”。這個(gè)時(shí)候,系統(tǒng)就會(huì)出現(xiàn)很長(zhǎng)的等待隊(duì)列,無法做出響應(yīng)。
當(dāng)系統(tǒng)使用率達(dá)到 100% 時(shí)就會(huì)出現(xiàn)很長(zhǎng)的等待隊(duì)列,無法對(duì)請(qǐng)求做出響應(yīng)。
我發(fā)現(xiàn)系統(tǒng)的重試次數(shù)通常被配置得很大,超時(shí)被配置得很長(zhǎng),這樣會(huì)增加工作負(fù)載,更有可能出現(xiàn)重試風(fēng)暴。之前我有深入地探討過這個(gè)問題,后來又新寫了一篇文章。更好的做法是使用較短的超時(shí)時(shí)間和單次重試,如果有其他可用實(shí)例,最好把請(qǐng)求發(fā)給它們。
- https://www.slideshare.net/adriancockcroft/evolution-of-microservices-craft-conference/29
- https://dev.to/aws/if-at-first-you-don-t-get-an-answer-3e85
系統(tǒng)的重試時(shí)間不能全部設(shè)置成一樣,前端部分應(yīng)該設(shè)置得長(zhǎng)一些,后端部分應(yīng)該設(shè)置得短一些。
一般情況下,人們會(huì)通過重啟來清楚超載的隊(duì)列,但一個(gè)經(jīng)過精心設(shè)計(jì)的系統(tǒng)會(huì)限制隊(duì)列的長(zhǎng)度,并通過丟棄請(qǐng)求或做出“快速失敗”的響應(yīng)來削減流量。數(shù)據(jù)庫和其他具有固定連接數(shù)限制的服務(wù)都使用了這種方式。當(dāng)你無法獲得可用的請(qǐng)求能力就會(huì)得到一個(gè)快速失敗的響應(yīng)。如果連接數(shù)限制設(shè)置得太低,數(shù)據(jù)庫會(huì)拒絕它本該有能力處理的請(qǐng)求,如果設(shè)置得太高,數(shù)據(jù)庫在拒絕更多請(qǐng)求之前就已經(jīng)變慢了。
要多想想當(dāng)系統(tǒng)使用率達(dá)到 100% 時(shí)該怎么辦,以及如何設(shè)置恰當(dāng)?shù)南拗七B接數(shù)。
要想在極端情況下還能保持較好的響應(yīng)速度,最好的方式是使用快速失敗響應(yīng)和削減流量。真實(shí)世界中的大部分系統(tǒng)即使是在正常情況下也會(huì)出現(xiàn)很多慢響應(yīng)。不過,我們可以通過正確的指標(biāo)監(jiān)控來解決這些問題,對(duì)系統(tǒng)進(jìn)行精心的設(shè)計(jì)和測(cè)試,這樣就有可能構(gòu)建出具有最大化響應(yīng)速度的系統(tǒng)。