效能分析小案例系列,可以通過下面連結檢視哦
https://www.cnblogs.com/poloyy/category/1814570.html
系統架構背景
- VM1:用作 Web 伺服器,來模擬效能問題
- VM2:用作 Web 伺服器的客戶端,來給 Web 服務增加壓力請求
- 使用兩臺虛擬機器(均是 Ubuntu 18.04)是為了相互隔離,避免交叉感染
VM2 執行 ab 命令,初步觀察 Nginx 效能
簡單介紹 ab 命令
- ab(apache bench)是一個常用的 HTTP 服務效能測試工具
- 可以向目標伺服器併發傳送請求
執行 ab 命令
併發 100 個請求測試 VM1 的 Nginx 效能,總共測試 1000 個請求
ab -c 100 -n 1000 http://172.20.72.58:10000/
從 ab 的輸出結果可以看到,Nginx 能承受的每秒平均請求數只有 145.68(有點差強人意)
那到底是哪裡出了問題呢
接下來,我們將通過一系列的命令來觀察哪裡出問題了
通過命令分析 VM1 的效能問題
VM2 長時間執行 ab 命令
併發 5 個請求,持續併發請求 10min
ab -c 5 -t 600 http://172.20.72.58:10000/
接下來的命令都在 VM1 上執行
top 檢視系統 CPU 使用率、程式 CPU 使用率、平均負載
top
結果分析
- 平均負載已遠超 CPU數量(2)
- Nginx、docker、php 相關的程式總的 CPU 使用率大概 40%左右
- 但是系統 CPU 使用率(us+sy)已達到 96%了,空閒 CPU(id)只剩下 3.7%
提出疑問
為什麼程式所佔用的 CPU 使用率並不高,但是系統 CPU 使用率和平均負載會這麼高?
回答疑問,分析程式列表
- containerd-shim:執行容器的,3.3% 的 CPU 使用率挺正常
- Nginx 和 php-fpm:執行 Web 服務的,佔用的 CPU 使用率也才 5-6%
- 再往後就沒有什麼佔用 CPU 使用率的程式了
嘶,發現 top 並沒有滿足我們的需求,看來得祭出另一個命令了
pidstat 檢視是否有異常程式的 CPU 使用率過高
每秒取一次結果,共取 10 次
pidstat 1 10
結果分析
跟 top 命令的結果差不多,Nginx、dockerd、php-fpm 的 CPU 使用率偏高,但是加起來並沒有使用者態 CPU 使用率這麼高
問題來了
使用者 CPU 使用率已經達到 55%,但卻找不到時哪個程式有問題,這到底是咋肥事?難道是命令太辣雞了嗎?
答案
命令的存在肯定是有它的意義,問題肯定是出在我們自己身上,是否遺漏了什麼關鍵資訊?
再次執行 top 命令
結果分析
發現了一個關鍵點,就緒佇列有 6 個程式處於 R 狀態
6 個正常嗎?
- 回想一下 ab 測試的引數,併發請求數是 5
- 再看程式列表裡, php-fpm 的數量也是 6, 再加上 Nginx
- 好像同時有 6 個程式也並不奇怪
仔細瞧一瞧
php-fpm、Nginx、docker 這麼多個程式只有 1 個 php-fpm 程式是 R 狀態的,其他都處於 S(Sleep)狀態,這就奇怪了
找到真正處於 R 狀態的程式
調整 top 列表排序規則
在 top 列表中,按 shift + f,進入 top 列表的顯示規則頁面
可以看到預設排序的列是 %CPU,這裡改成 S 列,就可以更快看到哪些程式的狀態是 R 了
來看看到底有哪些 R 狀態的程式呢
- 真正處於 Running(R)狀態的,卻是幾個 stress 程式
- 這幾個 stress 程式就比較奇怪了,需要做進一步的分析
觀察一段時間, stress 程式的變化
每次重新整理列表,stress 程式的數量都會變化,而且 PID(程式號)也會跟著變
更具體的動態檢視 stress 程式的變化
watch -d 'ps aux | grep stress | grep -v grep'
- 其實可以看到 stress 程式有三種狀態:R、S、Z
- 多個 stress 程式在不斷生成,由 R 變 S 再變 Z
- 三種變化:程式PID、程式數量、程式狀態
程式 PID 在不斷變化的可能原因
原因一:程式在不停地崩潰重啟
比如因為段錯誤、配置錯誤等等,這時,程式在退出後可能又被監控系統自動重啟
原因二:這些程式都是短時程式
- 就是在其他應用內部通過 exec 呼叫的外面命令
- 這些命令一般都只執行很短的時間就會結束,很難用 top 這種間隔時間比較長的工具發現
stress 程式 PID 為什麼在不斷變化
首先,我並沒有手動執行 stress 命令,所以它很大可能是通過其他命令去執行的
找到 stress 的父程式
pstress | grep stress
stress 是被 php-fpm 呼叫的子程式,並且程式數量不止一個(這裡是 2 個)
grep 查詢是否有程式碼在呼叫 stress
grep stress -r app/
可以看到, index.php 檔案是包含了 stress 命令
index.php 原始碼
<?php // fake I/O with stress (via write()/unlink()). $result = exec("/usr/bin/stress -t 1 -d 1 2>&1", $output, $status); if (isset($_GET["verbose"]) && $_GET["verbose"]==1 && $status != 0) { echo "Server internal error: "; print_r($output); } else { echo "It works!"; } ?>r
- 很明顯,就是 index.php 中呼叫了 stress 命令
- 每個請求都會呼叫一個 stress 命令,模擬 I/O 壓力
- stress 會通過 write() 、 unlink() 對 I/O 程式進行壓測
猜測
就是因為 stress 模擬 I/O 壓力導致的 CPU 使用率升高
靈魂拷問
如果是模擬 I/O 壓力,為什麼 pidstat 命令的時候 iowait% 並不高呢
看來得繼續深挖分析
VM2 帶引數傳送請求
index.php 有一句原始碼值得注意
if (isset($_GET["verbose"]) && $_GET["verbose"]==1 && $status != 0)
當請求傳了 verbose 引數時...
VM2 帶 verbose 引數傳送請求
curl http://172.20.72.58:10000?verbose=1
- 看到錯誤訊息 mkstemp failed: Permission denied ,以及 failed run completed in 0s
- 原來 stress 命令並沒有成功,它因為許可權問題失敗退出了
結果分析猜測
正是由於許可權錯誤,大量的 stress 程式在啟動時初始化失敗,過多的程式上下文切換,進而導致 CPU 使用率的升高
關於程式上下文切換的猜測驗證
未壓測前的系統上下文切換次數
只有 200 左右
壓測後的系統上下文切換次數
直蹦 10000 左右了,上下文切換次數的確漲了很多
stress 程式的上下文切換次數
前面是猜測 stress 程式有問題,那就來看看 stress 程式的上下文切換次數
pidstat -w 2 | grep stress
- 黃圈:自願上下文切換次數和非自願上下文切換次數並不高
- 藍圈:程式PID 一直在變化
靈魂拷問
為什麼 stress 程式的上下文切換次數這麼低?
回答
- 因為 stress 是短時程式
- 會頻繁的產生新的 stress 程式
- 系統需要從舊的 stress 程式切換到新的 stress 程式再執行,這樣切換次數就會增加
VM1 通過 perf 檢視效能分析報告
上一篇文章中也見到過這個 perf 命令
記錄效能事件
等待大約 15 秒後按 Ctrl+C 退出
perf record -g
檢視效能報告,結果分析
perf report
- 佔了 CPU 時鐘事件的前幾名都是 stress 程式
- 而且呼叫棧中比例最高的是隨機數生成函式 random() ,看來它的確就是 CPU 使用率升高的元凶了
擴充-swapper 程式是啥?
從上面報告其實可以看到排在前面的還有 swapper 程式,那麼它是啥呢?
- swapper 跟 SWAP 沒有任何關係
- 它只在系統初始化時建立 init 程式,之後,它就成了一個最低優先順序的空閒任務
- 也就是說,當 CPU 上沒有其他任務執行時,就會執行 swapper
- 所以,你可以稱它 為“空閒任務”
- 當沒有其他任務時,可以看到 swapper 基本佔滿了 CPU 時鐘事件
- 它的主要耗費都是 do_idle,也就是在執行空閒任務
優化問題
只要修復許可權問題,並減少或刪除 stress 的呼叫
總結
- CPU 使用率升高的主要原因就是短時程式 stress 頻繁的進行程式上下文切換
- 對於短時程式,我們需要通過 pstree 命令找到它的父程式,然後再分析父程式存在什麼問題
注意
當然,實際生產環境中的問題一般都要比這個案例複雜,在你找到觸發瓶頸的命令列後,卻可能發現,這個外部命令的呼叫過程是應用核心邏輯的一部分,並不能輕易減少或者刪除;這時,你就得繼續排查,為什麼被呼叫的命令,會導致 CPU 使用率升高或 I/O 升高等問 題
分析整體思路
- 短時間壓測,發現伺服器效能低下
- 長時間壓測,讓伺服器保持一個高負載的狀態,從而可以慢慢分析問題所在
- 通過 top 命令監控系統資源情況,發現使用者態的 CPU 使用率(us)較高,且空閒 CPU (id) 很低
- 但是找不到使用者態 的 CPU 使用率很高的程式,最高就 6%
- 進一步通過 pidstat 檢視是否有 CPU 使用率異常高的程式
- 發現 pidstat 行不通,再次通過 top 命令仔細觀察
- 發現 Running(R)狀態的程式有 6 個之多,但是 CPU 使用率較高的程式並沒有處於 R 狀態
- 切換 top 列表的排序規則,倒序,集中看 R 狀態的程式一段時間,發現是 stress 程式,而且時多時少,程式 PID 還會變化
- 通過 watch 實時觀察 stress 程式,發現 stress 程式會從從 R 狀態變到 S 再變到 Z,而且會不斷生成新的 stress 程式
- 猜測 stress 程式可能是短時程式,通過其他程式進行呼叫它
- 通過 pstree 找到 stress 程式的父程式,發現是 php-fpm 程式,接下來就是分析父程式了
- 通過 grep 查詢 stress 命令是否存在 Nginx 應用中,發現存在 index.php 檔案中
- 檢視原始碼,確認每次請求都會觸發 stress 命令
- stress 命令可以模擬 I/O 壓力的,通過 top 看到 iowait 其實並不算高
- 在 VM2 傳送帶請求引數,可以確認 stress 命令是執行失敗的
- 可以猜測大量的請求進來,導致大量的 stress 程式初始化執行失敗,從而增加程式上下文切換次數增加,最終導致 CPU 使用率升高
- 通過 vmstat 對比壓測前後的上下文切換次數,可以發現壓測的上下文切換次數的確增加了
- 通過 pidstat 檢視 stress 的上下文切換次數,但發現並不高,其實是因為 stress 是短時程式,上下文切換次數主要增加在舊程式切換到新程式的執行上
- 通過 perf record 錄製效能事件 30s
- 通過 perf report 檢視效能報告,可以看到佔用 CPU 時鐘事件的前幾名都是 stress 程式,然後排程比例最高的是 random 函式
- 確認問題根源就是 stress 程式呼叫了 random 函式
炒雞重點
- 其實有時候 top、vmstat、pidstat 命令用完了可能還不一定能發現問題根源
- 這個時候就要更加細心的檢視這些命令返回結果其中的貓膩,雖然可能找不到問題根源,但可能會發現某些線索
- perf 雖然強大,但並不是一開始分析就適合用它的