對於一個網站,已知服務端的服務線程數和處理單個請求所需的時間時,該如何算出高並發時用戶從點擊鏈接到收到響應的時間?注意這個時間並不等於服務端處理單個請求的時間,因為高並發時,很多用戶請求需要排隊等待,你要把這個額外的等待時間算進去。
這個問題很重要,因為它的結果直接影響你的網站的用戶體驗。這篇文章就是來幫你算這個時間的。你可以使用本文附帶的程序來算,也可以通過本文提煉出的公式來算。
另外還有一個問題:所謂RT(響應時間)和QPS,究竟要不要考慮用戶請求的排隊等待時間? 本文認為,對於RT我們應該區分出“服務器眼中的RT”和“客戶端眼中的RT”,但對於QPS卻不必區分。
后文會辨析這些概念,同時還會給出不同場景下,RT和QPS隨着並發數變化而變化的曲線圖。你可以通過比較不同的曲線,獲得一些收獲。
下面先推演一個最簡單的場景,通過這個場景,你可以了解到本文在計算時使用的基本思路。
最簡單的場景:服務端線程數=1,客戶端並發=2
如果服務端只有1個服務線程,每處理1個請求需要1個ms; 當客戶端在同一瞬間提交兩個請求時,各項性能指標是怎樣的?
為了解這個題,可以想象一下每個請求被處理的過程:
請求1被立即處理;由於服務線程只有一個,請求2需要等待請求1完結后才能被處理。
據此算出各項性能指標:
性能指標 |
指標值 |
說明 |
服務端響應所有請求的時間 |
1ms + 1ms = 2ms |
上圖中紫色時間之和 |
服務端響應每個請求的平均時間 |
服務端處理所有請求的時間/2 = 1ms |
|
客戶端所有請求的等待、響應時間 |
1ms + (1ms + 1ms) = 3ms |
第1個請求直接處理 = 1ms 第2個請求先等待后處理 = 1ms + 1ms |
客戶端每個請求的平均等待、響應時間 |
客戶端所有請求的等待、響應時間/2 = 1.5 ms |
|
看上面高亮的這些字,你應該已經了解了時間推算的基本思路了;后文其他場景中會使用相同的模式來推算。
再來觀察一下客戶端每個請求的平均等待、響應時間。它的值是1.5ms,大於服務端響應每個請求的平均時間1ms.
不同角度有不同的看法,
1. 服務端站在自己的角度來看:並發為2時,處理每個請求消耗了自己1ms, RT = 1ms
2. 客戶端站在自己的角度來看:並發為2時,每個請求平均需要花1.5ms才能被完結,服務器的RT應用1.5ms ,比服務端宣稱的1ms要高多了。
這兩種說法都有道理。就像銀行櫃員一直在忙,沒有偷懶,問心無愧;而排隊的客戶苦苦等待,牢騷漫天,其實也很無辜。
因此本文認為,RT應分為兩種:
性能指標 |
指標值 |
說明 |
服務端眼中的RT |
1ms |
不計入客戶端的等待時間 |
客戶端眼中的RT |
1.5ms |
計入客戶端的等待時間 |
順便再算一下此時的QPS. 事實上無論在服務端眼里還是客戶端眼里,QPS都是:
成功處理的請求數/(最后一個請求結束時間 – 第一個請求開始時間)= 2 / (2 – 0)ms = 1000
接下來再看看其它場景。
場景2:服務端線程數 > 1
上文說了服務端線程數=1的情況,如果服務端線程數超過1會怎么樣? 比如,服務端線程數 = 3, 客戶端並發 = 10:
指標值 |
|
服務端響應所有請求的時間 |
1ms * 3 + 1ms * 3 + 1ms * 3 + 1ms = 10ms |
服務端響應每個請求的平均時間 |
服務端處理所有請求的時間/10 = 1ms |
客戶端所有請求的等待、響應時間 |
1ms * 3 + 2ms * 3 + 3ms * 3 + 4ms = 22ms |
客戶端所有請求的等待、響應時間/10 = 2.2 ms |
這個推算並不難,但如果總是需要這樣手工推算就很煩了。因此,本文提供了一個程序來將推算步驟自動化。不過,在體驗這個程序之前,我們先推演一下其他的場景。
場景3:無過載保護,服務器響應能力受並發數影響
以上的場景都假設系統有嚴格的“過載保護”:只提供有限的服務線程數,由於系統壓力不會過載,所以每個線程都總是能以最高的速度來處理請求。
現實中,很多系統並沒有這種做,相反:
1. 沒有過載保護,來一個請求啟一個線程,直到系統崩潰
2. 當客戶端並發數超過一定閾值后,服務端處理單個請求的時間開始上升(原因很多,比如CPU、內存資源消耗過大,線程上下文切換過多等)
舉個例子,一個應用在並發<=3時,處理單個請求只需1ms;當並發超過3時,處理單個請求的能力開始惡化,
3個並發 => 處理單個請求需1ms
5個並發 => 處理單個請求需要5ms
…
10個並發 => 處理單個請求需要10ms
如果客戶端並發=10,性能會是什么狀況?
性能指標 |
指標值 |
服務端響應所有請求的時間 |
10 * 10ms = 100ms |
服務端響應每個請求的平均時間 |
服務端處理所有請求的時間/10 = 10ms |
客戶端所有請求的等待、響應時間 |
10 * 10ms = 100ms |
客戶端每個請求的平均等待、響應時間 |
客戶端所有請求的等待、響應時間/10 = 10 ms |
沒有過載保護時,所有請求都不用排隊;這時推算性能的算法會比較簡單。接下來再看最后一種場景。
場景4:弱過載保護
有很多應用,在過載保護方面是上面兩種機制的折衷:
1. 在客戶端並發數低於某閾值時,單個請求的處理速度可以保持全速
2. 當客戶端並發數高於上述閾值但還不太高時,單個請求的處理速度有所下降,但仍可以接受,系統這時願意容忍這種程度的並發,以換取更高的吞吐量(QPS)
3. 當客戶端並發數過大、高於另一個閾值時,單個請求的處理速度會迅速惡化,應該在這個點進行過載保護
本文把這種策略稱作“弱過載保護”,場景2稱作“強過載保護”,場景3則是“無過載保護”。
弱過載保護系統的性能該如何推算?
1. 並發數低於第一個閾值時,所有請求不必等待。此時的計算方法跟“無過載保護”基本相同,只不過這時單個請求的處理時間是個常量。
2. 並發數高於第一個閾值但低於第二個閾值時,所有請求仍不必等待,此時的計算方法跟“無過載保護”完全一致。
3. 並發數高於第二個閾值時,有些請求開始等待,此時的計算方法跟“強過載保護”場景下的模型基本一致,只不過這時單個請求的處理時間 = 最高並發時的單個請求的處理時間。
另外,“強過載保護”和“無過載保護”可以視為“弱過載保護”的特殊情況。
1. “強過載保護”也是一種“弱過載保護”,它的第二個閾值 = 第一個閾值
2. “無過載保護”也是一種“弱過載保護”,它的二級閾值 = ∞
既然所有的場景都可以統一為同一種機制,我們可以搞出一個“統一的性能 - 並發數計算模型”。
統一的性能 - 並發數計算模型
輸入:
|
符號 |
公式中的符號 |
|
服務器端處理能力 |
第1個線程數閾值 |
serverThreshold1 |
|
第1個線程數閾值前的單個請求處理時間 |
serverResponse1 |
||
第2個線程數閾值 |
serverThreshold2 |
||
兩個閾值之間的單個請求處理時間函數 |
serverResponse2Func (clientConcurrency) |
||
客戶端壓力 |
客戶端並發數 |
clientConcurrency |
輸出:
|
符號 |
公式中的符號 |
備注 |
|
服務器眼中的性能 |
服務器響應所有請求的時間 |
serverResponseSum |
|
|
服務器響應每個請求的平均時間 |
serverResponseAverage |
serverResponseSum/clientConsurrency |
||
客戶端眼中的性能 |
客戶端所有請求的等待、響應時間 |
clientWaitResponseSum |
|
|
客戶端每個請求的平均等待、響應時間 |
clientWaitResponseAverage |
clientWaitResponseSum/clientConsurrency |
算法:
參照下圖,推算出每個請求的處理時間和響應時間,然后分別求和、求平均.
通過程序自動化計算
按照上圖的模式來手工推演性能會很煩的。本文提供了一個開源程序,以將推算過程自動化。
如果急於看效果,可以
2. 執行 java -jar how-long-to-wait-1.0.0-SNAPSHOT-jar-with-dependencies.jar 這個DEMO算出了serverThreshold =3, 並發=10的各項性能指標
想自定義參數,則可以:
1. 下載http://how-long-to-wait.googlecode.com/files/how-long-to-wait-1.0.0-SNAPSHOT-project.zip
2. 解壓,作為Maven工程放到你的IDE里
3. 模仿org.googlecode.hltwsample.single.ShowCase寫你自己的代碼
這個程序還會記錄每個請求被執行的起止時間,你可以利用它來驗算
…
默認情況下執行時間會被直接打印到控制台,但你也可以擴展實現自己的記錄展現工具。具體方法是,
1. 實現SingleAppSimulateEventListener接口
2. 再把它注入到singleAppVisitSimulator.setEventListener()
通過公式計算
你也可以直接通過公式來計算性能。
服務端眼中的RT:
客戶端眼中的RT:
得
性能-並發數曲線圖
前面一直在做定量分析,現在可以搞一下定性分析了:從數據中找到可以輔助決策的結論。
我們可以讓並發數持續升高,同時觀察服務器/客戶端眼中性能的差異,以及同一性能指標在不同過載保護機制下的差異。
服務端處理能力:
第1個線程數閾值 |
10 |
第1個線程數閾值前的單個請求處理時間 |
10ms |
第2個線程數閾值 |
20 |
單個請求處理時間函數 |
無過載保護時的性能趨勢圖
1. 並發數超過一個極限之前,RT基本不變,但QPS會持續上升,表明系統的並發服務能力被利用得越來越充分
2. 無過載保護時,當並發數超過一個極限后,系統性能會急劇下降
3. 無過載保護時,所有請求會被服務端立即處理,沒有等待時間,因此客戶端和服務器的RT曲線完全吻合。
強過載保護時的性能趨勢圖
1. 強過載保護時,服務器跟中的永遠是條水平線,因為服務器一直處於最佳狀態
2. 強過載保護時,客戶端眼中的系統性能在並發數經過一個閾值后逐漸惡化,實際上這只是排隊時間在增多而已。
3. 強過載保護時,QPS的變化比較平穩,因為服務器一直處於健康狀態
弱過載保護時的性能趨勢圖
1. 弱過載保護時,RT會先后經歷兩次較為平緩的性能下降。第一次是由於服務器本身性能下降,第二次則是因為客戶端的排隊時間增多。
2. 弱過載保護時,QPS的變化仍算比較平穩
不同過載保護機制下的性能比較
放大下面兩條曲線:
在服務器眼里,保護越強,則性能越好
放大下面兩條曲線:
1. 在客戶端眼里,有過載保護比沒有好
2. 在上圖中,弱過載保護時的性能比強過載保護時的RT要平一些,但這是因為本圖里的函數本身比較平坦,使得系統能夠在較高並發時仍以較好的速度處理請求,而且總體性能更好。 如果
函數很陡的話,情況就會是另外一種樣子了。所以,強過載保護好還是弱過載保護好,要具體案例具體分析。
結語
本文給出的程序和公式可以幫助你在已知服務端性能的條件下,計算出客戶端的平均等待、相應時間。
本文區別了服務器眼中的RT和客戶端眼中的RT,並且通過曲線圖介紹了不同過載保護機制下性能如何隨並發數變化而變化。
不過,本文的推算劇本中,客戶端只會發一次請求:若並發為10,則一次性提交10個請求。而現實中的客戶端應該會持續不斷的提交請求,這時候它們的平均等待、響應時間又是多少呢? 這個問題要結合客戶端本身發送請求的速率來計算,我們以后再深入地看看。