性能分析小案例系列,可以通過下面鏈接查看哦
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 雖然強大,但並不是一開始分析就適合用它的