Categories
程式開發

我從性能測試中學到的那些經驗與教訓


本文要點:

  • 要做好性能測試很難,但花一些額外的時間做好性能測試是值得的,這樣可以避免一些反復出現的問題;
  • 要注意區分延遲測試和吞吐量測試,因為它們是系統的兩個不同的方面;
  • 在進行延遲測試時,要把吞吐量固定在一個水平;
  • 在進行聚合時不要丟棄任何一個與性能有關的信息,要盡可能多地把它們記錄下來;
  • 只要多加小心,再加以自動化,就可以最大程度地避免出現性能回退。

對於像Hazelcast這樣的內存計算平台來說,性能意味著一切。因此,我們在性能方面投入了大量精力,並在這個方面積累了多年的經驗。

在這篇文章中,我將介紹一些常見的問題,並分享如何做好常規的性能測試。

性能測試中反復出現的問題

首先,測試環境差異是最常見的一個問題。如果一個系統最終要被部署到多台具有超強處理能力的機器上,那就不能夠只根據在本地開發機上進行的性能測試做出合理的假設,因為在將系統部署到真實的環境中,結果會完全不一樣。有趣的是,人們比他們自己認為的更容易犯這種錯誤。在我看來,其中的主要原因是懶惰(在本地測試很容易,但搭建額外的測試機器需要時間),意識不到這會成為一個問題,有時候是因為缺乏資源。

另一個反復出現的問題是測試場景不真實,例如,負載不夠高、並發線程太少,或者完全不一樣的操作比率。通常來說,在測試時要盡可能接近真實場景。也就是說,在開始測試之前,我們要盡可能多地收集與測試、用例、測試目標、環境和場景有關的信息,盡可能做到與真實環境相似。

下一個問題是不區分吞吐量測試和延遲測試,有時候甚至不確定是不是真的關心這兩種測試。從我的經驗來看,客戶通常會更關心應用程序的吞吐量,於是他們就盡可能壓榨系統的吞吐量,並基於這些結果做出決策。但如果你深挖下去,你會發現,在99%的場景中,他們的負載都只是介於每秒多少個操作之間。在這個時候,可以把系統的吞吐量固定在這個水平,然後觀察延遲,並進行延遲測試,而不是吞吐量測試。

最後一個問題是只關心聚合指標,比如均值和中值。這些指標把很多信息隱藏掉了,而這些被隱藏的信息很可能有助於做出更好的決策。本文的“評測性能”一節將更詳細地介紹應該要收集哪些信息。

延遲測試和吞吐量測試之間的區別

吞吐量基本上是指某段時間內完成的操作次數(通常是每秒多少次操作)。延遲,也就是響應時間,是指從執行一個操作開始到接收到結果之間的時間。

這兩個最基本的指標之間通常是相互關聯的。在非並行系統中,延遲與吞吐量是成反向關係的,反過來也是一樣。簡單地說,如果每秒可以完成10個操作,那麼每個操作(平均)需要十分之一秒。如果每秒种完成的操作越多,那麼每個操作需要的時間就越少。

但是,在並行系統中,這種關係很容易被打破。以在Web服務器中增加線程為例,這樣做不會縮短每個操作的響應時間,也就是說延遲不變,但吞吐量卻提升了。

因此,吞吐量和延遲實際上是系統的兩個不同的指標,我們要單獨測試它們。更確切地說,在進行延遲測試時,我們要把吞吐量固定在一個水平,例如,“在每秒鐘10萬個操作的前提下測試延遲”。如果不這麼做,在不同的吞吐量下系統延遲會發生變化,也就失去了可比性。

我們得到的經驗教訓是:在進行延遲測試時要使用固定的吞吐量。

收集和比較性能測試結果

另一個問題是如何收集、記錄和分析性能測試結果。我覺得可以分為兩個部分:一個是不要丟棄信息,一個是把分散的點連接起來。

我經常看到性能基準測試報告裡會顯示每秒平均操作數、中值或者延遲的某種百分位。的確,這樣的報告看起來很簡潔,也很容易發佈出去。但是,如果你真的關心性能問題,就不要這麼簡單粗暴,你需要看到“所有”的數據。

在進行吞吐量測試時,一個常見的做法是講總的操作數除以時間,這樣做很容易就把重要信息丟掉了。如果你想要看到整體的情況,就需要顯示吞吐量的變化過程。

我從性能測試中學到的那些經驗與教訓 1

從這張圖可以很容易地找到問題所在。根據簡單的“每秒平均X次操作”,你可以快速地得出結論:綠線是最好的。但是,這張圖看起來又有點奇怪。雖然綠線的吞吐量是最大的,但是也是最不穩定的。換句話說,綠線抖動得很厲害,一點也不平滑。我們基於這條線發現了線程調度問題。

總的來說,這張圖顯示了吞吐量的變化過程,但如果只是從聚合結果來看,是不可能發現問題的。我們可能會因為綠線的值最高而歡呼雀躍,但卻忽略了一個重要的性能問題。

在進行延遲測試時,另一個常見的問題是只顯示平均延遲,或者好一點的話會顯示某種百分位。在理想情況下,你可以創建一個百分位直方圖,讓你可以看到整體的情況。你還可以做得更好,就是查看百分位隨時間的變化情況。

作為例子,下圖顯示了50百分位隨著時間發生變化的情況。

我從性能測試中學到的那些經驗與教訓 2

從圖中我們可以清楚地看到延遲隨著時間的變化而增長。如果我們只發布平均值或者是一個完整的直方圖,就很難發現這些信息。我們知道,系統發生了內存洩露。

Gil Tene的HdrHistogram是一個非常好的用來生成百分位和延遲圖表的工具。

然後是同時查看所有的圖表。如果你在其中的一張圖中發現了問題,要在另一張圖中進行確認。如果有性能問題,通常會在多個地方體現出來。

我們再以內存洩露為例。如果一個Java程序發生內存洩露,我們會看到內存使用量上升,這個時候吞吐量應該會下降,因為垃圾回收會佔用更多的時間。與此相關聯的是,延遲也可能會增加。我們也可以看一下線程的CPU時間,它們的CPU時間也會減少,因為垃圾回收佔用了更多的CPU時間。信息越多,你就會得到更好的結論,就越容易找到解決問題的方案。

找出性能瓶頸

找出性能問題的根源通常是一個漫長而痛苦的過程,取決於具體的代碼、測試人員的經驗,有時候甚至是運氣。但我們仍然是有跡可循的。

首先是逐步診斷,每次只診斷一個步驟。在進行性能調優時,我們通常會說“這個選項很好用,這樣設置垃圾回收對性能有幫助,打開這個開關可以獲得更好的結果,我們都試試吧”。最終,這種疊加效果導致我們搞不清楚哪個才是有效的。因此,每一次我們只走一步,只使用一個開關,只修改一個地方。

具體來說,我們可以利用所有可利用的工具,特別是在手機數據的時候。你掌握的信息越多,就越是能夠更好地了解系統行為,更快地找到痛點。因此,我們需要盡可能收集所有信息:系統息息(CPU、內存、磁盤IO、上下文切換)、網絡信息(對於分佈式系統來說尤為重要)、垃圾回收日誌、Java Flight Recorder(JFR)的分析數據,等等。我們還開發了專有的診斷工具,更細粒度地直接收集內部信息——操作時間取樣、內部線程池的大小和時間、內部管道和緩衝區的統計信息,等等。

我從性能測試中學到的那些經驗與教訓 3

上圖比較了三種調優選項的不同點,顯示了每秒鐘系統上下文切換次數。從結果看,綠線的切換次數最少,說明系統做的有用的事情更多,這個可以從吞吐量那張圖中得到驗證。

防止性能回退

進行常規(每天)自動化性能測試是很有必要的。在引入變更時需要盡可能快地獲取必要的數據。在發現性能回退後,我們可以很容易將其隔離出來,極大減少用於修復回退所需的時間。

一個是執行測試,一個是保存和分析結果。在Hazecast,我們使用PerfRepo,一個開源的Web應用程序,用於保存和分析性能測試結果。它會更新每一個最新的性能測試結果,所以很容易發現性能回退——你會在圖中看到線往下走。我在Red Hat工作期間積極維護這個項目,因為時間原因,現在的開發進度慢了一些,但仍然是個完全可用的項目。

但不管怎麼樣,限制還是有的。我們無法面面俱到,因為各自情況的組合幾乎是無限的。我們傾向於選擇“最重要”的那些,但具體怎麼做仍然值得我們討論。

結論

要做好性能測試很難,很多方面都會出問題。關鍵在於要多注意細節,了解系統的行為,避免出現一些可笑的結果。當然,這需要時間。在Hazelcast,我們也很注意這些,我們願意為此付出時間和精力。你們呢?

作者介紹

Jiři Holuša是一個專注於開源的軟件工程師,他熱愛他的工作。之前在Red Hat工作,目前是Hazelcast質量工程團隊的負責人。 Hazelcast是一家開發內存計算平台的公司。 Holuša喜歡深挖一個問題,從來都不放棄,直到問題得到解決。除此之外,他喜歡運動,作為一個真正的捷克人,他喜歡在喝啤酒時與別人進行愉快的交談。如果你想看到更多由Holuša提供的有關性能測試的演講,可以看他在2019年歐洲TestCon大會上做的Performance Testing Done Right演講。你可以在Twitter通過@jholusa聯繫他。

原文鏈接

Lessons Learned in Performance Testing