如何利用火焰圖定位 Java 的 CPU 性能問題


 

 常見 CPU 性能問題

你所負責的服務(下稱:服務)是否遇到過以下現象:

  • 休息的時候,手機突然收到大量告警短信,提示服務的 99.9 line 從 20ms 飆升至 10s;
  • 正在敲代碼實現業務功能時,收到業務/客服同事電話,反饋系統打不開;
  • 下班后,收到運維同學電話,服務器監控告警提示“某個機器的負載(Load Average)從 0.1、0.5、0.8 突然間飆升至 9.73、10.67、10.49”。

結果:引發雪崩的場景如下圖所示:

 

 通常造成這幾種現象的根本原因主要有以下 3 點。

  • 服務在某個時間點運行了某一個定時器,但因為最近業務數據量增加,導致提取了大量數據到服務進行計算。
  • 外部系統因為系統問題/數據量增加/邏輯修改,向消息中間件推送比平常多 10 倍的消息,此時服務消費了 MQ 消息,將消息內容放入一個新線程進行異步處理。而給消息中間件返回消息已處理完成,導致消息中間件一直給服務推送消息。
  • 上線一個可以從 C 端觸發需要大量 CPU 計算的功能,上線后使用的用戶比預估量高 5 倍。

 

我們再來回顧一下之前的過程,如下圖所示:

 

從現象到結果(CPU 問題),再到追溯原因這一過程,通常是通過對時間點、查日志、找上下游問題、Code Review、推測出問題代碼,從而得出最終結論。但這種方式的缺點是耗時較長,且不能保證得到的結果一定是真實的。

利用工具定位 CPU 性能問題

借助Flame Graph(下稱 CPU 火焰圖),來定位 CPU 性能問題。

在實際工作中,使用 CPU 火焰圖用於量化框架中的性能,包括代碼編譯消耗的時間、代碼緩存、其他系統內庫及內核代碼執行的時間,通常用於定位 CPU 使用率問題。生成火焰圖的方式有基於 Perf 或者Arthas。

利用 Perf 生成火焰圖

從 Linux 系統原生提供的性能分析工具 perf 命令(performance 的縮寫)說起,該命令執行后,會返回 CPU 正在執行的函數名以及調用棧(stack)。 通常,它的執行頻率是 99Hz(每秒 99 次)。如果 99 次都返回同一個函數名,那就證明 CPU 同一秒鍾都執行同一個函數,可能存在性能問題。具體可以通過執行以下 perf 命令獲取對應的性能日志:

1 perf record -F 99 -p PID -g

該命令返回一個用於記錄 CPU 調用棧的文本文件,該文件長達幾十萬甚至上百萬行,輸出的日志文件並不方便閱讀,所以需要有一個將日志文件轉成火焰圖的工具——perf report。

1 perf report -n --stdio

perf report 命令可以很好地將數百個堆棧跟蹤樣本匯總為文本。類似的代碼路徑合並在一起,文本輸出的信息為樹形圖,而每個葉子上都有 CPU 占用的百分比。從左上角到右下角讀取路徑,

  • perf 可以通過命令生成 CPU 在執行什么,但並不能直接顯示 Java 進程的調用棧。那用什么方式可以與 Java 進程的調用棧結合起來生成火焰圖呢?
  • perf_events:標准 Linux 分析器,用於生成系統堆棧。
  • perf-map-agent:提供轉換 perf_events 帶有 Java 標示的 JVMTI 代理。
  • Misc:生成全部 Java 進程的堆棧信息。
  • Flame Graph:通過已經生成的 Java 堆棧信息,生成火焰圖。

火焰圖種類

火焰圖(Flame Graph):最普通/朴素的火焰圖,標示了每一個調用棧的深度與執行時間。

 

紅藍微分火焰圖(Differential Flame Graph):有兩個值列,用於顯示計數之前和之后。使用第二列(“之后”或“現在”)調整火焰圖的大小,然后使用 2-1 的增量進行着色:正極為紅色,負極為藍色。pl 工具獲取兩個折疊樣式的概要文件(使用 stackcollapse 腳本生成)並發出這兩列輸出。

 

 

CPI 火焰圖(CPI Flame Graph):測量每條指令的平均周期(CPI)比率,越高的值意味着完成指令需要更多的周期(通常指等待內存 I/O 的“停滯周期”),往往作為一個全系統的指標來研究。

CPI(平均每條指令的平均時鍾周期數)=(CPU 時間/IC 指令數)*頻率

 

耗時分析方法

 

 

請看以上實際的火焰圖例子,例子中包含各種疊起來並且大小不一的圖形,那如何得知哪個方法耗時最多?下面我們來分析一下如何閱讀上述火焰圖。

  • Y 軸:棧深度
  • X 軸:CPU 耗時
  • 長方形:一個棧(方法)
  • 長度:出現在監視器中的時長(占用 CPU 的時間)
  • 其他:從左到右的順序只是按照字母排序,並無意義

通過上述分析,我們知道了圖中各種數字的含義。因為下面調用棧的耗時是依賴於上面調用棧,所以越往上的調用棧長方形越長,CPU 在該調用棧的耗時越多。因此,上圖中耗時最多的方法應該是 f()。

總結一下,閱讀火焰圖的方式主要有以下兩種。

  • 從下到上:從父到子方法追查。
  • 從左到右:先找出占用最多時間的棧,關注最寬的方法。

如何排查線上 CPU 問題

定位到 CPU 問題后,接下來開始排查線上問題。

  • 壓測環境:在壓測環境,我們可以通過壓測與監控工具,發現性能問題。利用 CPU 火焰圖,在壓測時,通過對 CPU 進行定時采樣,定位具體占用 CPU 調用棧。
  • 模擬環境:建立一個基於線上,但與其隔離的模擬環境。通過流量復制工具,將線上流量復制到模擬環境。再通過對 CPU 進行定時采樣,將采樣的日志生成火焰圖,從而定位具體占用 CPU 調用棧。

下圖是一個線上發生 CPU 性能問題的例子:

 

 

如果線在頂部代表 CPU 空閑,線在底部代表 CPU 繁忙。該問題是在用戶量不多的服務中發生的,此時可以先回顧一下問題的過程。

時間點 1 發布了一個包含監控功能與業務功能的版本。慢慢地 CPU 開始繁忙起來。到時間點 2,CPU 使用率超過 50%,持續了 3 小時。在時間點 3 實施過重啟操作,CPU 使用率回歸正常,但慢慢地也在往上增加。在時間點 4 ,回滾業務功能,但CPU 沒有降下來。在時間點 5 實施回滾監控功能,服務恢復正常。

在上述處理線上 CPU 問題的過程中,采取了回滾版本讓服務恢復正常。但如何才能定位 CPU 在哪里被占用最多呢?這時候,CPU 火焰圖就需要登場了。

在壓測環境,使用壓測工具(ab/wrk/jmeter 等)對服務的 URL 進行隨機參數的請求。發現 CPU 在壓測開始 10 分鍾之后,CPU 慢慢上升,服務響應越來越慢。此時使用 perf 工具對 CPU 進行采樣,通過火焰圖工具將采樣的日志生成包含調用棧的火焰圖。

 

 

從上圖中可以看出,大部分時間在執行服務中的邏輯,指向新加的服務監控組件。但此時問題在於:監控組件創建 span 時應該是根據路徑作為 key,指向已緩存的數據可以加快處理時間。

而實際情況則是將參數也放到了緩存 key 中,所以緩存沒命中,導致key一直在創建,長期地占用 CPU 在計算 key 是否有命中,從而產生 CPU 性能問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM