爲什麼你的服務會變慢?

你開發了一個服務,調用它,它做了一些事情並返回結果。那麼,它需要花多長時間?爲什麼有時候它花的時間比用戶期望的要長?在這篇文章中,我將從最基礎的講起,然後逐步介紹一些標準的術語,同時着重強調一些需要知道的關鍵點。

首先,我們需要一種方式來度量時長,還需要理解兩個完全不同的度量角度。從調用服務的外部用戶角度來看,我們需要度量響應時間。從服務處理請求的角度來看,我們需要度量服務時間。這就引出了第一個關鍵點,人們常常分不清一些指標。

對於用戶來說是響應時間(Response Time),對於服務來說是服務時間(Service Time)。

在真實世界裏,每一個處理過程都包含了很多步驟,每個步驟都需要佔用一些時間。步驟佔用的時間叫作駐留時間(Residence Time),駐留時間由等待時間(Wait Time)和服務時間組成。以用戶登錄 App 爲例,一個用戶登錄手機 App,App 會調用 Web 服務進行用戶認證。爲什麼有時候會很慢?按理說,每一次手機上生成請求的時間、將請求傳輸給 Web 服務的時間、查詢用戶的時間、返回結果並顯示下一個屏幕的時間應該是一樣的。造成響應時間長短不一的是排隊時間,也就是等待正在處理其他請求的資源。從手機到認證服務器之間的網絡傳輸需要經過很多跳,每一跳前面都有等待被髮送的數據包。如果隊列是空的或者隊列很短,那麼響應速度就很快,如果隊列很長,響應就很慢。當請求達到服務器時,也需要排隊等待 CPU 處理。如果需要查詢數據庫,還需要排到另一個隊列裏。

排隊等待是導致響應時間增加的主要原因。

監控工具會提供一個叫作吞吐量(Throughput)的指標,用來度量處理頻度。在某些情況下,我們也會得到一個叫作到達率(Arrival Rate)的指標,用於度量到達服務器的請求的速率。在理想情況下,比如一個具有穩定工作負載狀態的 Web 服務,一個請求對應一個響應,那麼吞吐量和到達率是一樣的。不過,重試和錯誤會導致到達率增加,但吞吐量不會增加。對於快速變化的工作負載或者需要長時間處理的請求(比如批次作業),會出現到達率和吞吐量之間的不均衡,併產生更爲複雜的請求模式。

吞吐量是指已經成功處理完的請求數量,它跟達到率是不一樣的。

我們可以通過一些跟蹤系統(比如 Zipkin 或 AWS X-Ray)來跟蹤單個請求流,不過我們這裏討論的是大量請求以及它們之間的交互關係。我們通過固定的時間間隔來度量均值,時間間隔可以是秒、分鐘、小時或天。計算均值需要足夠多的數據,一般來說每個均值至少需要 20 個數據點。

如果請求不是很頻繁,請選擇一個至少包含 20 個請求的時間間隔,這樣纔有可能得到比較有用的信息。

如果選擇的時間間隔太大,會導致工作負載的變化被隱藏掉。例如,對於視頻會議系統來說,大部分會話會在一個小時的頭一分鐘左右啓動,並且很容易在這些時間段達到峯值,讓系統發生過載,如果時間間隔是小時,這些信息就會丟失掉。所以,對於這種情況,時間間隔設爲秒更爲恰當。

對於變化快的工作負載,可以使用秒級的均值。

監控工具各種各樣,但一般很少會直接告訴我們等待隊列有多長或有多少併發度可用來處理隊列。大多數網絡每次只傳輸一個數據包,但 CPU 的每個核心或 vCPU 可以並行處理隊列裏的任務。數據庫通常有一個固定的最大連接數,用來限制併發度。

對於處理請求的每一個步驟,可以記錄或估計用於處理請求的併發度。

如果系統運行穩定,有穩定的平均吞吐量和響應時間,那就很容易估算等待隊列的長度,只需要將吞吐量和駐留時間相乘即可。這就是所謂的利特爾法則法則(Little’s Law)。這個法則很簡單,監控工具經常用它來估算隊列長度,但它只對具有穩定均值的系統有效。

根據利特爾法則,平均隊列長度 = 平均吞吐量 * 平均駐留時間。

爲了更好地理解這個法則的原理,我們需要知道請求是如何到達服務器以及請求之間的間隔是怎樣的。如果我們通過循環進行簡單的性能測試,請求之間的間隔是固定的,那麼利特爾法則就無效,因爲這樣出現的隊列很短,而且這樣的測試不真實。我們通常會進行這樣的測試,以爲很完美,但是在將服務部署到生產環境之後,眼睜睜地看着它越跑越慢,吞吐量越來越低。

這種速率固定的循環測試不會有隊列出現,它們只是在模擬傳送帶。

在真實的網絡世界中,用戶都是獨立的,他們發送自己的請求,不同用戶發送的請求之間的間隔是隨機的。所以,在測試時,我們需要使用可以生成具有隨機等待時間的請求的生成器。大多數系統會使用隨機分佈,雖然比模擬傳送帶要好,但也是不對的。要模擬真實的網絡流量,並讓利特爾法則生效,我們需要使用負指數分佈(Negative Exponential Distribution)。Neil Cunther 博士在這篇文章中解釋了什麼是負指數分佈。

要生成更加真實的隊列,需要使用恰當的隨機時間算法。

但問題是,真實的網絡流量並不是隨機分佈的,而是帶有爆發性質的。想象一下,當一個用戶打開一個手機 App,它不會只發出一個請求,而是很多個。在網絡購物搶購活動中,會有很多用戶同時打開 App,這會導致流量爆發。這種分佈形態叫作帕累託或雙曲線。另外,當網絡經過重新配置,流量會被延遲,就會出現隊列,而隊列會給下游系統帶來閃電式的衝擊。Jim Brady 和 Neil Gunther 寫了一些腳本,演示如何配置測試工具,從而獲得更加真實的流量。Jim Brady 還寫了一篇關於如何知道負載測試好壞的論文

相比常用測試工具默認生成的流量負載,真實世界的流量負載更具爆發性,會導致更長的等待隊列和響應時間。

等待隊列和響應時間應該是變化的,而且即使是在使用率很低的時候也會出現一些很慢的請求處理速度。那麼,當處理步驟中的某一步開始變慢時會怎樣?當使用率增加,一些處理步驟沒有足夠的可用資源(比如網絡傳輸),那麼請求相互爭奪資源的情況就會增加,駐留時間也會增加。一般來說,當使用率達到 50% 到 70% 時,網絡就會逐漸變慢。

將網絡使用率保持在 50% 以下可用獲得更好的延時。

對於並行度高的 CPU,在使用率較高的情況下,速度會變得更慢,影響也更大,大到令你喫驚。如果你將最後一個可用的 CPU 看作爭用點,那麼這就很直觀了。例如,如果有 16 個 vCPU,最後可用的 CPU 具有 6.25% 的處理能力,那麼使用率就是 93.75%。對於具有 100 個 vCPU 的系統,它的使用率約爲 99%。在穩定狀態下,公式 R=S/(1-U^N) 可用來近似估算隨機到達服務器的請求的行爲。

在多處理器系統中,隨着使用率的增加,平均駐留時間的膨脹會減少,但強度卻增加了。

使用率使用比例,而不是百分比,並將其作爲處理器核數的冪底數。用 1 減掉使用率的 N 次冪,再用平均服務時間除以結果,就可以估算出平均駐留時間。如果使用率很低,平均駐留時間就會很接近平均服務時間。如果一個網絡的 N=1 並且使用率爲 70%,那麼用平均服務時間除以 0.3,得到的平均駐留時間就是低使用率時的三倍。

通常情況下,我們需要將平均駐留時間保持在 2 到 3 倍以下,這樣才能獲得更短的用戶響應時間。

對於一個有 16 個 vCPU 並且使用率爲 95% 的系統,0.95^16=0.44,再用平均服務時間除以 0.56,就會得到兩倍的平均駐留時間。如果使用率爲 98%,那麼 0.98^16=0.72,再用平均服務時間除以 0.28,平均駐留時間就會變慢,變得不可接受,而此時使用率僅增加了 3%。

當多處理器系統的使用率很高時,一個很小的負載變化就會產生很大的影響,這是多處理器系統的一個問題。

Unix/Linux 系統有一個指標叫作負載平均(Load Average),人們通常對它瞭解得不夠透徹,它存在一些問題。Unix 系統(包括 Solaris、AIX、HPUX)會記錄運行中的和等待 CPU 的線程數,Linux 還會記錄等待 IO 阻塞的線程數,然後還使用了三種時間衰減值,分別是 1 分鐘、5 分鐘和 15 分鐘。首先我們需要知道的是,這個指標可以追溯到 60 年代的單核 CPU 時代,所以我通常會將負載平均值除以 vCPU 的數量,從而得到具有可比性的值。其次,這個指標與其他指標不一樣,它沒有使用固定的時間間隔,所以它們不屬於同一種平均值。第三,這個指標在 Linux 上的實現已經成了一個 bug,被制度化成一個系統特性,其結果也被誇大了。

負載平均這個指標不度量負載,也不是均值,所以最好把它忽略掉。

如果一個系統超載,請求達到的速度超過了處理能力,使用率就達到了 100%,那麼上面的那個公式的除數就是 0,這樣會導致駐留時間無窮大。在實際當中,這種情況會更加糟糕,因爲當系統變慢時,上游的用戶會發送重試請求,這樣會加大系統的負載,出現“重試風暴”。這個時候,系統就會出現很長的等待隊列,無法做出響應。

當系統使用率達到 100% 時就會出現很長的等待隊列,無法對請求做出響應。

我發現系統的重試次數通常被配置得很大,超時被配置得很長,這樣會增加工作負載,更有可能出現重試風暴。之前我有深入地探討過這個問題,後來又新寫了一篇文章。更好的做法是使用較短的超時時間和單次重試,如果有其他可用實例,最好把請求發給它們。

系統的重試時間不能全部設置成一樣,前端部分應該設置得長一些,後端部分應該設置得短一些。

一般情況下,人們會通過重啓來清楚超載的隊列,但一個經過精心設計的系統會限制隊列的長度,並通過丟棄請求或做出“快速失敗”的響應來削減流量。數據庫和其他具有固定連接數限制的服務都使用了這種方式。當你無法獲得可用的請求能力就會得到一個快速失敗的響應。如果連接數限制設置得太低,數據庫會拒絕它本該有能力處理的請求,如果設置得太高,數據庫在拒絕更多請求之前就已經變慢了。

要多想想當系統使用率達到 100% 時該怎麼辦,以及如何設置恰當的限制連接數。

要想在極端情況下還能保持較好的響應速度,最好的方式是使用快速失敗響應和削減流量。真實世界中的大部分系統即使是在正常情況下也會出現很多慢響應。不過,我們可以通過正確的指標監控來解決這些問題,對系統進行精心的設計和測試,這樣就有可能構建出具有最大化響應速度的系統。

英文原文

Why are services slow sometimes?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章