傾囊相授之性能分析思路

年輕的時候,經常聽一些大會或者演講。有些人說,思路邏輯非常重要。我那時就想,你肯定是瞎忽悠的,因爲我怎麼就沒聽懂你說的思路呢?

而現在輪到自己來寫或者講一些東西的時候,才發現他們說得很對,而我之所以不理解,也是有原因的。

性能分析思路和具體的實現之間,有一道鴻溝,那就是操作的能力。之前我爲什麼聽不懂那些人的思路,其實是因爲我沒有操作的功底。

而有了操作的功底之後,還有一個大的鴻溝要越過去,那就是從操作到對監控計數器的理解。這一步可以說讓很多性能測試人員都望而卻步了。

但是這還不算完,這一步邁過去之後,還有一個跳躍,就是相關性分析和證據鏈分析的過程。

如此一來,就會得到一張性能測試分析的能力階梯視圖,如下:

工具操作:包括壓力工具、監控工具、剖析工具、調試工具。

數值理解:包括上面工具中所有輸出的數據。

趨勢分析、相關性分析、證據鏈分析:就是理解了工具產生的數值之後,還要把它們的邏輯關係想明白。這纔是性能測試分析中最重要的一環。

最後纔是調優:有了第 3 步之後,調優的方案策略就有很多種了,具體選擇取決於調優成本和產生的效果。 

那麼怎麼把這些內容都融會貫通呢?下面我們就來說說性能測試分析的幾個重要環節。

應該說,從我十幾年的性能工作中,上面講的這些內容是我覺得最有價值的內容了。在今天的文章中,我們將對它做一次系統的說明。我先把性能分析思路大綱列在這裏:

  1. 瓶頸的精準判斷;
  2. 線程遞增的策略;
  3. 性能衰減的過程;
  4. 響應時間的拆分;
  5. 構建分析決策樹;
  6. 場景的比對。

一、瓶頸的精準判斷

TPS 曲線

對性能瓶頸做出判斷是性能分析的第一步,有了問題才能分析調優。

之前有很多人在描述性能測試的過程中,說要找到性能測試中曲線上的“拐點”。我也有明確說過,大部分系統其實是沒有明確的拐點的。

舉例來說,TPS 的視圖如下:

顯然,這是一個階梯式增加的場景,非常好。但是拐點在哪呢?有人說,顯然在 1200TPS 左右的時候。也有人說了,顯然是到 1500TPS 纔是拐點呀。但是也有人說,這都已經能到 2000TPS 了,顯然 2000TPS 是拐點。

我們再來看一下這張圖對應的響應時間視圖:

 是不是有人要說響應時間爲 4.5ms 時是拐點了?

其實這些對拐點的判斷,都是不合理的。如果我們對 TPS 的增加控制得更爲精準的話,那麼這個 TPS 的增加是有一個有清晰的弧度,而不是有一個非常清晰的拐點。

但是至少我們可以有一個非常明確的判斷,那就是瓶頸在第二個壓力階梯上已經出現了。因爲響應時間增加了,TPS 增加得卻沒有那麼多,到第三個階梯時,顯然增加的 TPS 更少了,響應時間也在不斷地增加,所以,性能瓶頸在加劇,越往後就越明顯。

那麼我們的判斷就是:

有瓶頸!

瓶頸和壓力有關。

壓力呈階梯,並且增長幅度在衰減。

如果你覺得上面的瓶頸還算清晰的話,那麼我們再來看一張圖:

 在這個 TPS 的曲線中,你還能判斷出拐點在哪嗎?

顯然是判斷不出來拐點的,但是我們根據圖得出以下幾個結論:

有瓶頸!

瓶頸和壓力有關。

壓力也是階梯的,但是並沒有明確的拐點。

我們再來看一個 TPS 圖:

看到這張圖,是不是明顯感覺系統有瓶頸呢?那麼瓶頸是不是和壓力大小有關呢?

這種比較有規律的問題,顯然不是壓力大小的原因。爲什麼呢?因爲 TPS 週期性地出現降低,並且最大的 TPS 也都恢復到了差不多的水位上。所以,即使是壓力降低,也最多降低最大的 TPS 水位,會讓問題出現得更晚一點,但是不會不出現。

綜合以上,如果畫一個示意圖的話,TPS 的衰減過程大概會如下所示:

  1. 隨着用戶數的增加,響應時間也在緩慢增加。
  2. TPS 前期一直都有增加,但是增加的幅度在變緩,直到變平。

在這樣的趨勢圖中,我們是看不到明確的拐點的。但是我們能做的清晰的判斷就是:有瓶頸!所以對 TPS 曲線來說,它可以明確告訴我們的就是:

  1. 有沒有瓶頸:其實準確說所有的系統都有性能瓶頸,只看我們在哪個量級在做性能測試了。
  2. 瓶頸和壓力有沒有關係:TPS 隨着壓力的變化而變化,那就是有關係。不管壓力增不增加,TPS 都會出現曲線趨勢問題,那就是無關。

時你可能會問,爲什麼不看響應時間就武斷地下此結論呢?其實響應時間是用來判斷業務有多快的,而 TPS 纔是用來判斷容量有多大的。

響應時間的曲線

 我們還是來看看響應時間,下面看一張響應時間圖:

它對應的線程圖是:

多明顯的問題,隨着線程的增多,響應時間也在增加,是吧。再來看它們對應的 TPS 圖:

到第 40 個線程時,TPS 基本上達到上限,爲 2500 左右。響應時間隨着線程數的增加而增加了,系統的瓶頸顯而易見地出現了。

但是,如果只讓你看 TPS 曲線,你是不是也會有同樣的判斷?那就是:有瓶頸!並且和壓力有關?所以說,其實 TPS 就可以告訴我們系統有沒有瓶頸了,而響應時間是用來判斷業務有多快的。

後面我們還會提到響應時間會是性能分析調優的重要分析對象。

二、線程遞增的策略

講完響應時間之後,我們再來看下線程遞增。

在見識了很多性能測試人員做的場景之後,必須得承認,有些場景的問題太多了。

首先,我們來看兩個場景的執行對比。

場景 1 的線程圖:

場景 1 的 TPS 圖:

場景 1 的響應時間圖:

場景 2 的線程圖:

場景 2 的 TPS 圖:

場景 2 的響應時間圖:

這兩個場景的比對如下:

有了這些對比數據之後,你是不是覺得哪裏似乎是有問題的? 

對的!

TPS 都是達到 400,但兩個場景中線程遞增的策略不同,產生的響應時間完全不同。雖然都沒有報錯,但是第一種場景是完全不符合真實的業務場景的。這是爲什麼呢?

在場景的執行過程中,首先,響應時間應該是從低到高的,而在場景 1 中不是這樣。其次,線程應該是遞增的,而場景 1 並沒有這樣做(這裏或許有人會想到秒殺的場景,認爲場景 1 符合秒殺的業務設定,這個問題我們稍後提及)。最後,在兩個場景中,TPS 的上限都達到了 400TPS。但是你可以看到,在場景 2 中,只要 40 個線程即可達到,但場景 1 中居然用到了 500 線程,顯然壓力過大,所以響應時間才那麼長。

其實在生產環境中,像場景 1 這樣的情形是不會出現的。如果它出現了,那就是你作爲性能測試的責任,因爲你沒有給出生產環境中應該如何控制流量的參數配置說明。

同時,我們從上面的場景對比可以看到,對一個系統來說,如果僅在改變壓力策略(其他的條件比如環境、數據、軟硬件配置等都不變)的情況下,系統的最大 TPS 上限是固定的。

場景 2 使用了遞增的策略,在每個階梯遞增的過程中,出現了抖動,這就明顯是系統設置的不合理導致的。設置不合理,有兩種可能性:1. 資源的動態分配不合理,像後端線程池、內存、緩存等等;2. 數據沒有預熱。

我們再回到之前說的秒殺場景。

說到秒殺場景,有人覺得用大線程併發是合理的,其實這屬於認識上的錯誤。因爲即使線程數增加得再多,對已經達到 TPS 上限的系統來說,除了會增加響應時間之外,並無其他作用。所以我們描述系統的容量是用系統當前能處理的業務量(你用 TPS 也好,RPS 也好,HPS 也好,它們都是用來描述服務端的處理能力的),而不是壓力工具中的線程數。這一點,我在第 5 篇文章中已經做了詳細的解析,你可以回去再看看。

那麼,對於場景中線程(有些工具中叫虛擬用戶)遞增的策略,我們要做到以下幾點:

  1. 場景中的線程遞增一定是連續的,並且在遞增的過程中也是有梯度的。
  2. 場景中的線程遞增一定要和 TPS 的遞增有比例關係,而不是突然達到最上限。後面在場景的篇幅中我們會再說它們之間的比例關係。
  3. 上面兩點針對的是常規的性能場景。對於秒殺類的場景,我們前期一定是做好了系統預熱的工作的,在預熱之後,線程突增產生的壓力,也是在可處理範圍的。這時,我們可以設計線程突增的場景來看系統瞬間的處理能力。如果不能模擬出秒殺的陡增,就是不合理的場景。

這裏給出我做性能場景遞增的經驗值:

當然這裏也不會是放在哪個系統中都適合的遞增幅度,你還是要根據實際的測試過程來做相應的判斷。

有了這些判斷之後,相信大家都能做出合理的場景來了。

三、性能衰減的過程

有了瓶頸的判斷能力,也有了線程遞增的意識,那麼下面在場景執行中,我們就要有判斷性能衰減的能力了吧。 

來,我們先看一個壓力過程中產生的結果圖。

在遞增的壓力過程中,隨着用戶數的增加。我們可以做幾次計算。

第一次計算,在線程達到 24 時,TPS 爲 1810.6,也就是每線程每秒發出 75.44 個請求。

第二次計算,在線程達到 72 時,TPS 爲 4375.1,也就是每線程每秒發出 60.77 個請求。

第三次計算,在線程達到 137 時,TPS 爲 5034,也就是每線程每秒發出 36.74 個請求。

通過這三次計算,我們是不是可以看到,每線程每秒發出的請求數在變少,但是整體 TPS 是在增加的。

我們有很多做性能測試的人,基本上,只看 TPS 和響應時間的時候,在上面這個示例中,肯定會一直往上加用戶。雖然響應時間在增加,但是增加得也不多嘛。

但實際上,通過我們的計算可以知道,性能是在不斷地衰減的。我們來看一張統計圖:

通過紅線的大致比對可以知道,當每線程每秒的請求數降到 55 左右的時候,TPS 就達到上限了,大概在 5000 左右,再接着往上增加線程已經沒有用了,響應時間開始往上增加了。 

這就是性能衰減的過程(題外話,在上圖中,其實還有一個問題,就是在紅線前面,性能在上升的過程中有幾次抖動,這個抖動到後面變大了,也變頻繁了,如果這是必然出現的抖動,那也是配置問題,希望你注意到這一點)。

爲什麼要這麼細緻地描述性能衰減的過程呢?

其實我就是想告訴你,只要每線程每秒的 TPS 開始變少,就意味着性能瓶頸已經出現了。但是瓶頸出現之後,並不是說服務器的處理能力(這裏我們用 TPS 來描述)會下降,應該說 TPS 仍然會上升,在性能不斷衰減的過程中,TPS 就會達到上限。

這也是前面我說的,性能瓶頸其實在最大 TPS 之前早就已經出現了。

那麼我們是不是應該在性能衰減到最大 TPS 時就停止場景呢?這個不一定的哦。

因爲停不停場景,取決於我們的場景目標,如果我們只是爲了得到最大 TPS,那確實可以停止場景了。但是,如果我們要擴大化性能瓶頸,也就是說爲了讓瓶頸更爲明顯,就完全不需要停止場景,只要不報錯,就接着往上壓,一直壓到我們要說的下一個話題——響應時間變長,需要拆分。

四、響應時間的拆分

在性能分析中,響應時間的拆分通常是一個分析起點。因爲在性能場景中,不管是什麼原因,只要系統達到了瓶頸,再接着增加壓力,肯定會導致響應時間的上升,直到超時爲止。

在判斷了瓶頸之後,我們需要找到問題出現在什麼地方。在壓力工具上看到的響應時間,都是經過了後端的每一個系統的。

那麼,當響應時間變長,我們就要知道,它在哪個階段時間變長了。

我們看下這張圖。

這應該是最簡單的一個壓力測試邏輯了。一個應用,一個 DB,結果也拆分出了 8 個時間段,這還是在我沒有加上壓力工具自己所消耗的時間的情況下。

如果我們要分析壓力工具中的響應時間,拆分的邏輯就是上面這個示意圖。

但是在真實的場景中,基本上不是這樣的。如果是內網,那基本上都是連在一個交換機上,所以通常是這樣的:

在這樣的拓撲中,我們仍然可以拆出來 t1 到 t8 的時間。只是實際動手的時候,思路一定要清晰,時間拆分是從哪裏到哪裏,要畫出來,不能混亂。

我們有很多手段可以進行時間的拆分,當然要看我們的應用支持哪一種。

如果我們是這樣的架構,拆分時間應該是比較清楚的。

 首先我們需要查看 Nginx 上的時間。日誌裏就可以通過配置 requestt​imeupstream_response_time 得到日誌如下信息:

14.131.17.129 - - [09/Dec/2019:08:08:09 +0000] "GET / HTTP/1.1" 200 25317 0.028 0.028

最後兩列中,前面是請求時間的 28ms,後面是後端響應時間的 28ms。同時,我們再到 Tomcat 上去看時間。


172.18.0.1 - - [09/Dec/2019:08:08:09 +0000] "GET / HTTP/1.1" 200 25317 28 27 http-nio-8080-exec-1

請求時間消耗了 28ms,響應時間消耗了 27ms。接着再來看一下前端的時間消耗:

從這裏可以看到,從發出請求到接收到第一個字節,即 TTFB 是 55.01ms,內容下載用了 11.75ms。從這就可以看得出 Nginx 基本上沒消耗時間,因爲它和 Tomcat 上的請求響應時間非常接近。

那麼網絡上的消耗時間怎麼樣呢?我看到有很多人用 TTFB 來描述網絡的時間。先來說明一下,TTFB 中顯然包括了後端一系列處理和網絡傳輸的時間。如下圖所示

下面的紫色點是指要接收的內容。上面的紅色線就是 TTFB。

如果接收完了呢?就是這個狀態:

所以,我覺得用 TTFB 描述網絡的健康狀態並不合理。如果用 Content Download 來描述會更爲合理。比如我們上面的這個例子中,那就是 11.75ms 下載了 25317 Bytes 的內容。

Tomcat 上基本上是消耗了處理的所有時間,當然這中間也包括了 MySQL 花費的時間。而前端看到的其他時間就消耗在了網絡中。

在這個例子中,主要說明了響應時間怎麼一步步拆。當然,如果你是下面這種情況的話,再一個個拆就比較辛苦了,需要換另一種方式。

你肯定想知道每個系統消耗了多長時間,那麼我們就需要鏈路監控工具來拆分時間了。比如像這樣來拆分:

從 User 開始,每個服務之間的調用時間,都需要看看時間消耗的監控。這就是時間拆分的一種方式。

其實不管我們用什麼樣的工具來監控,最終我們想得到的無非是每個環節消耗了多長時間。用日誌也好,用鏈路監控工具也好,甚至抓包都可以。

當我們拆分到了某個環節之後,就有了下一步的動作:構建分析決策樹。

五、構建分析決策樹

關於分析決策樹,我在很多場合也都有提及。

分析決策樹,對性能測試分析人員實在是太重要了,是性能分析中不可或缺的一環。它是對架構的梳理,是對系統的梳理,是對問題的梳理,是對查找證據鏈過程的梳理,是對分析思路的梳理。它起的是縱觀全局,高屋建瓴的指導作用。

性能做到了藝術的層級之後,分析決策樹就是提煉出來的,可以觸類旁通的方法論。

而我要在這裏跟你講的,就是這樣的方法論。

應該說,所有的技術行業在面對自己的問題時,都需要有分析決策樹。再廣而推之的話,所有的問題都要有分析決策樹來協助。

通過上面的幾個步驟,我們就會知道時間消耗在了哪個節點上。那麼之後呢?又當如何?

總要找到根本的原因纔可以吧,我畫了如下的分析決策圖:

從壓力工具中,只需要知道 TPS、響應時間和錯誤率三條曲線,就可以明確判斷瓶頸是否存在。再通過分段分層策略,結合監控平臺、日誌平臺,或者其他的實時分析平臺,知道架構中的哪個環節有問題,然後再根據更細化的架構圖一一拆解下去。

我在這裏,以數據庫分析和操作系統分析舉一下例子。

首先我們看一下數據庫分析決策樹。

比如針對 RDBMS 中的 MySQL,我們就可以畫一個如下的決策樹:

由於這裏面的內容實在過多,無法一次性展現在這裏。我舉幾個具體的例子給你說明一下。

MySQL 中的索引統計信息有配置值,有狀態值。我們要根據具體的結果來判斷是否需要增加 key_buffer_size 值的大小。比如這種就無所謂了。

Buffer used     3.00k of   8.00M  %Used:   0.04

從上面的數據可以看到,key buffer size 就用到了 4%,顯然不用增加。

再比如,我們看到這樣的數據:

       __Tables_______________________
    Open             2000 of 2000    %Cache: 100.00
    Opened         15.99M     4.1/s

這就明顯有問題了。配置值爲 2000 的 Open Table Cache,已經被佔滿了。顯然這裏需要分析。但是,看到狀態值達到配置值並不意味着我們需要趕緊加大配置值,而是要分析是否合理,再做相應的處理。比如說上面這個,Table 確實打開得多,但是如果我們再對應看下這一條。

    Slow 2 s        6.21M     1.6/s

你是不是覺得應該先去處理慢 SQL 的問題了?

關於數據庫的我們就不舉更多的例子了。在這裏只是爲了告訴你,在分析決策樹的創建過程中,有非常多的相互依賴關係。

然後我們再來看一下操作系統分析決策樹,我在這裏需要強調一下,操作系統的分析決策樹,不可以繞過。

如果你想到操作系統架構圖就頭大,那麼這時候應該覺得有了希望。那就是我覺得操作系統上的問題判斷是比較清晰的,所以基於此決策樹,每個人都可以做到對操作系統中性能問題的證據鏈查找。

 但是!對嘛,總得有個但是。

對操作系統的理解是個必然的前提。我看過很多人寫的操作系統性能分析方面的書籍或資料,發現大部分人把描述計數器的數值當成性能分析。

怎麼理解這句話呢?比如說:

“CPU 使用率在 TPS 上升的過程中,從 10% 增加到 95%,超過了預期值。” “內存使用率達到 99%,所以是瓶頸點。” “I/O 使用率達到 100%。” 等等。

這樣的描述,在我的性能團隊中,一定會被罵回去重寫。我要這些描述有什麼用?我要的是爲什麼達到了這樣的值,原因在哪?怎麼解決?

就像分析決策樹中所描述的那樣,性能工程師要做的是一步步地細化分析,給出最終的原因。

有人說,如果按這個路子,似乎操作系統的分析並不複雜嘛。大概三五個命令就可以跳到代碼層了。是的,對於操作來說,確實不多,但是對於判斷來說,那就複雜了。舉個例子來說明一下:

看到這樣的圖,你是不是有種手足無措的感覺?中斷能佔 40%,sy CPU 也能佔 40%。這系統還用幹業務的事嗎?全乾自己的事去了,可見操作系統有問題!你是不是要做這個判斷了?

而實際情況是,這個主機上只有一個網卡隊列,而請求量又比較大。

所以要解決的是網卡隊列的問題,至於怎麼解決,那手段就多了。可以換個服務器,可以多加幾個隊列,可以多接幾個節點…

以上只是給出幾個性能分析過程中常見的決策樹示例。在後續的分析過程實例中,我們將秉承着這種分析思路,一步步地走到瓶頸的面前。

六、場景的比對

爲什麼要寫這一部分呢?因爲我看到很多人對瓶頸的判斷,並不那麼精確,所以想寫一下場景比對的建議。

其實簡單來說,就一句話:當你覺得系統中哪個環節不行的時候, 又沒能力分析它,你可以直接做該環節的增加。

舉例來,我們現在有一個如下的架構:

可以得到這樣的結果:

從 TPS 曲線中,我們可以明顯看到系統是有瓶頸的,但是並不知道在哪裏。鑑於系統架構如此簡單,我們索性直接在某環節上加上一臺服務器,變成這樣:

然後得到如下數據:

 喲,沒好使!

 怎麼辦?再接着加其他節點,我加了更多的 JMeter 機器。

再來看下結果:

 真巧,TPS 增加了!

 看到了吧,這就是我說的場景比對。

當我們不知道系統中哪個環節存在性能瓶頸時,對架構並不複雜的系統來說,可以使用這樣的手段,來做替換法,以快速定位問題。

總結

在這一篇中,我說到了瓶頸的精準判斷、線程遞增的策略、性能衰減的過程、響應時間的拆分、構建分析決策樹以及場景的比對,這幾個環節,是性能分析過程中非常重要的環節。

從我的經驗上來說,這一篇文章可能是我工作十幾年的精華所在了。而這裏的每一個環節,又有非常多的細分,特別是構建分析決策樹這一塊,它需要太多的架構知識、系統知識、數據庫知識等等。鑑於本文只是想起到一個提綱挈領的作用,所以無法展開描述,希望在後續的篇幅中,我們儘量細緻拆解。

思考題

今天的內容雖然有點多,但總的來說,思路比較清晰,理解起來也比較容易。如果你認真學習了今天的內容,不妨思考兩個問題,爲什麼線程遞增過程不能斷?構建分析決策樹的關鍵是什麼?

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