在性能分析中,前端的性能工具,我們只需要關注幾條曲線就夠了:TPS、響應時間和錯誤率。這是我經常強調的。
但是關注TPS到底應該關注什么內容,如何判斷趨勢,判斷了趨勢之后,又該如何做出調整,調整之后如何定位原因,這才是我們關注TPS的一系列動作。
今天,我們就通過一個實際的案例來解析什么叫TPS的趨勢分析。
案例描述
這是一個案例,用一個2C4G的Docker容器做服務器。結構簡單至極,如下所示:
當用個人電腦(上圖中壓力工具1)測試雲端服務器時,達到200多TPS。但是當用雲端同網段壓力機(上圖中壓力工具2)測試時,TPS只有30多,並且內網壓力機資源比本地壓力機要高出很多,服務器資源也沒有用完。
在這樣的問題面前,我通常都會有一堆的問題要問。
- 現象是什么?
- 腳本是什么?
- 數據是什么?
- 架構是什么?
- 用了哪些監控工具?
- 看了哪些計數器?
在分析之前,這些問題都是需要收集的信息,而實際上在分析的過程中,我們會發現各種數據的缺失,特別是遠程分析的時候,對方總是不知道應該給出什么數據。
我們針對這個案例實際說明一下。
這個案例的現象是TPS低,資源用不上。
下面是一個RPC腳本的主要代碼部分。
public SampleResult runTest(JavaSamplerContext arg0) {
// 定義results為SampleResult類
SampleResult results = new SampleResult();
// 定義url、主機、端口
String url = arg0.getParameter("url");
String host = arg0.getParameter("host");
int port = Integer.parseInt(arg0.getParameter("port"));
results.sampleStart();
try {
message=detaildata_client.detaildata_test(url);// 訪問URL並將結果保存在message中
System.out.println(message); //打印message,注意這里
results.setResponseData("返回值:"+ message, "utf-8");
results.setDataType(SampleResult.TEXT);
results.setSuccessful(true);
} catch (Throwable e) {
results.setSuccessful(false);
e.printStackTrace();
} finally {
String temp_results=results.getResponseDataAsString();
results.setResponseData("請求值:"+arg0.getParameter("url")+"\n"+"返回值:"+temp_results, "utf-8");
results.sampleEnd();
}
return results;
JMeter腳本關鍵部分:
<stringProp name="ThreadGroup.num_threads">100</stringProp>
//我們來看這里,ramp_time只有1秒,意味着線程是在1秒內啟動的,這種場景基本上都和真實的生產場景不相符。
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
...............
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data File Config" enabled="true">
<stringProp name="delimiter">,</stringProp>
<stringProp name="fileEncoding">utf-8</stringProp>
<stringProp name="filename">filename</stringProp>
<boolProp name="ignoreFirstLine">false</boolProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames">url</stringProp>
</CSVDataSet>
在這個腳本中,邏輯非常簡單,一個RPC接口:1. 發出請求;2. 返回響應;3. 打印返回信息。
本機跑出來的結果如下:
在這個案例中,參數化數據就是根據真實的業務量來計算的,這個可以肯定沒有問題。
那么架構呢?在最上面的圖中已經有了部署的說明。在邏輯實現上,也就是一個很簡單的服務端,內部並沒有復雜的邏輯。所用到的監控工具是top、Vmstat。
看了哪些計數器呢?CPU、內存、I/O等。
下面我們開始分析。
第一階段
對公網上的測試來說,基本上壓力都會在網絡上,因為出入口帶寬會成為瓶頸,所以先要看一眼自己的帶寬用到了多少,再比對一下出口路由上的帶寬。
這里1Gbps只用到了0.01%,也就是(1000/8)x0.01%=12.5k(這里是將帶寬bit換成byte計算)。
在這樣的帶寬使用率之下,即使是公網也不見得會有問題,更別說在內網了。可見帶寬不是瓶頸點。
既然這樣,我們直接在內網里來做分析,看原因是什么。
但是我們要以什么樣的場景來跑呢?因為帶寬現在看到用得並不多,但TPS也上不去。首先應該想到的場景就是把TPS曲線給做出梯度來。
為什么要這么做?最重要的就是要知道到底TPS在多少壓力線程下會達到最大值,也就是我在各種場合經常強調的一個場景,最大TPS場景。關於這種曲線,我們不需要性能指標應該就可以做得出來。如下圖所示:
在一個既定場景、既定數據、既定環境的壓力場景中,我們一定要拿到這樣趨勢的TPS和RT曲線。其中綠色和紅色的點都是不需要業務指標來限定的,而是通過壓力場景中觀察TPS趨勢線來確定。
我來解讀一下這個趨勢圖:
- 響應時間一定是從低到高慢慢增加的;
- TPS一定也是從低到高慢慢增加的,並且在前面的梯度中,可以和線程數保持正比關聯。舉例來說,如果1個線程TPS是10,那2個線程的TPS要在20。依次類推。
而在這個例子中,前面有提到100線程1秒加載完,這樣的比例完全看不出來梯度在哪,所以,改為100秒加載100個線程,再來看看梯度。
測試結果如下:
從這個結果可以看出幾點:
1.TPS一點梯度沒看出來。為什么說沒有看出來呢?這里我發一個有明顯梯度的TPS曲線出來以備參考(這張圖不是本實例中的,只用來做分析比對):
2.響應時間增加的太快了,明顯不符合前面我們說的那個判斷邏輯。那什么才是我們判斷的邏輯呢?這里我發一個有明顯梯度的出來以備參考(這張圖不是本實例中的,只用來做分析比對):
- 粒度太粗,對一個duration只有五分鍾的場景來說,這樣的粒度完全看不出過程中產生的毛刺。
- 至少看到內網的TPS能到180了,但是這里沒有做過其他改變,只是把Ramp-up放緩了一些,所以我覺得這個案例的信息是有問題的。
第二階段
針對以上的問題,下面要怎么玩?我們列一下要做的事情。
- 將Ramp-up再放緩,改為300秒。這一步是為了將梯度展示出來。
- 將粒度改小,JMeter默認是60秒,這里改為1秒。這一步是為了將毛刺顯示出來。強調一點,如果不是調優過程,而是為了出結果報告的話,粒度可以設置大一些。至於應該設置為多大,完全取決於目標。
接着我們再執行一遍,看看測試結果:
這樣看下來,有點意思了哈。明顯可以看到如下幾個信息了。
- 響應時間隨線程數的增加而增加了。
- TPS的梯度還是沒有出來。
顯然還是沒有達到我們說的梯度的樣子。但是這里我們可以看到一個明顯的信息,線程梯度已經算是比較緩的了,為什么響應時間還是增加得那么快?
這里的服務器端壓力情況呢?如下所示:
從監控圖大概看一下,服務端CPU、內存、網絡幾乎都沒用到多少,有一種壓力沒有到服務端的感覺。
在這一步要注意,壓力在哪里,一定要做出明確的判斷。
在這里,當我們感覺服務端沒有壓力的時候,一定要同時查看下網絡連接和吞吐量、隊列、防火牆等等信息。查看隊列是非常有效的判斷阻塞在哪一方的方式。
如果服務端的send-Q積壓,那就要查一下壓力端了。如下所示:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
......
LISTEN 0 54656 :::10001 :::*
......
在網絡問題的判斷中,我們一定要明確知道到底在哪一段消耗時間。我們來看一下發送數據的過程:
從上圖可以看出,發送數據是先放到tcp_wmem
緩存中,然后通過tcp_transmit_skb()
放到TX Queue中,然后通過網卡的環形緩沖區發出去。而我們看到的send-Q就是Tx隊列了。
查看壓力端腳本,發現一個問題。
System.out.println(message);
一般情況下,我們在調試腳本的時候會打印日志,因為要看到請求和響應都是什么內容。但是壓力過程中,基本上我們都會把日志關掉。一定要記住這一點,不管是什么壓力工具,都要在壓力測試中把日志關掉,不然TPS會受到很嚴重的影響。
了解JMeter工具的都知道-n參數是命令行執行,並且不打印詳細的返回信息的。但是這里,一直在打印日志,並且這個日志在JMeter中執行時加了-n參數也是沒用的。
這樣一來,時間全耗在打印日志中了。知道這里就好辦了。我們在這里做兩件事:
- 把打印日志這一行代碼注釋掉,再執行一遍。
- 把ramp-up時間再增加到600秒。
為什么我要執着於把ramp-up時間不斷增加?在前面也有強調,就是要知道TPS和響應時間曲線的趨勢。
在性能分析的過程中,我發現有很多性能工程師都是看平均值、最大值、最小值等等這些數據,並且也只是描述這樣的數據,對曲線的趨勢一點也不敏感。這是完全錯誤的思路,請注意,做性能分析一定要分析曲線的趨勢,通過趨勢的合理性來判斷下一步要做的事情。
什么叫對曲線的趨勢敏感?就是要對趨勢做出判斷,並且要控制曲線的趨勢。
有時,我們經常會看到TPS特別混亂的曲線,像前面發的TPS圖一樣,抖動幅度非常大,這種情況就是完全不合理的,在遇到這種情況時,一定要記得降低壓力線程。
你可能會問,降到多少呢?這里會有一個判斷的標准,就是一直降到TPS符合我們前面看到的那個示意圖為止。
再給你一個經驗,如果實在不知道降多少,就從一個線程開始遞增,直到把梯度趨勢展示出來。
第三階段
通過注釋掉打印日志的代碼,可以得到如下結果:
從TPS曲線上可以看到,梯度已經明顯出來了。在有一個用戶的時候,一秒就能達到1000多TPS,並且在持續上升;兩個線程時達到2500以上,並且也是在持續上升的。
從響應時間上來看,也是符合這個趨勢的,前面都在1ms以下,后面慢慢變長。
壓力越大,曲線的毛刺就會越多,所以在TPS達到6000以上后,后面的TPS在每增加一個線程,都會出現強烈的抖動。
在這種情況下,我們再往下做,有兩條路要走,當然這取決於我們的目標是什么。
- 接着加壓,看系統什么時候崩潰。做這件事情的目標是找到系統的崩潰點,在以后避免出現。
- 將線程最大值設置為10,增加ramp up的時間,來看一下更明確的遞增梯度,同時分析在線程增加過程中,系統資源分配對TPS的影響,以確定線上應該做相對應的配置。
總結
在這個案例中,我們將TPS從150多調到6000以上,就因為一句日志代碼。
我分析過非常多的性能案例,到最后發現,很多情況下都是由各種簡單的因素導致的,這一反差也會經常讓人為這一路分析的艱辛不值得。
但我要說的是,性能分析就是這樣,當你不知道問題在哪里的時候,有一個思路可以引導着你走向最終的原因,那才是最重要的。
我希望通過本文可以讓你領悟到,趨勢這個詞對曲線分析的重要性。在本文中,我們通過對曲線的不合理性做出判斷,你需要記住以下三點:
- 性能分析中,TPS和響應時間的曲線是要有明顯的合邏輯的趨勢的。如果不是,則要降線程,增加Ramp-up來讓TPS趨於平穩。
- 我們要對曲線的趨勢敏感,響應時間的增加不可以過於陡峭,TPS的增幅在一開始要和線程數對應。
- 當TPS和響應時間曲線抖動過於強烈,要想辦法讓曲線平穩下來,進而分析根本原因,才能給出線上的建議配置。
思考題
今天我結合案例具體說明了下如何分析TPS的趨勢,如果你吸收了文章的內容,不妨思考一下這兩個問題?
- Ramp-up配置有什么樣的作用?
- 為什么說壓力工具中TPS和響應時間曲線抖動過大會不易於分析?
歡迎你在評論區寫下你的思考,也歡迎把這篇文章分享給你的朋友或者同事,一起學習交流一下。
精選留言:
答:Ramp-up 配置的時間是指啟動所有配置的線程總數所用的時間,例如設置的線程總數為500,Ramp-up設置的時間為50s,意為:啟動500個線程數需要50s,平均為每一秒啟動10個線程
為什么說壓力工具中 TPS 和響應時間曲線抖動過大會不易於分析?
答:性能分析一定要分析曲線的趨勢,通過趨勢的合理性來判斷性能瓶頸所在的原因,光靠平均值、最大值、最小值、中位數是無法確切的分析處壓測過程中服務器的具體情況,只有通過分析曲線趨勢,增加對趨勢的敏感程度才是壓測過程中更好的保障和前提。 [3贊]