性能分析小案例系列,可以通過下面鏈接查看哦
https://www.cnblogs.com/poloyy/category/1814570.html
前言
- 前面兩個案例講的都是上下文切換導致的 CPU 使用率升高
- 這一篇就來講講等待 I/O 導致的 CPU 使用率升高的案例
進程狀態
詳解進程狀態
https://www.cnblogs.com/poloyy/p/13413770.html
不可中斷狀態
- 當 iowait 升高時,進程很可能因為得不到硬件的響應,而長時間處於不可中斷狀態
- 不可中斷也是為了保護進程數據和硬件狀態一致,並且正常情況下,不可中斷狀態在很短時間內就會結束
- 所以,短時的不可中斷進程,一般可以忽略
- 但如果系統或硬件發生了故障,進程可能會在不可中斷狀態保持很久,甚至導致系統中出現大量不可中斷進程。這時,就得注意下,系統是不是出現了 I/O 等性能問題
僵屍進程
多進程引用很容易碰到的問題
正常情況
- 一個進程創建了子進程后,它應該通過系統調用 wait() 或 waitpid() 等待子進程結束,回收子進程的資源
- 而子進程在結束時,會向它的父進程發送 SIGCHLD 信號
- 所以,父進程還可以注冊 SIGCHLD 信號的處理函數,異步回收資源
異常情況
- 如果父進程沒有回收資源,或是子進程執行太快,父進程還沒來得及處理子進程狀態,子進程就已經提前退出,那這時的子進程就會變成僵屍進程
- 形象比喻:父親應該一直對兒子負責, 善始善終,如果不作為或者跟不上,都會導致“問題少年”的出現
重點
- 僵屍進程持續的時間都比較短,在父進程回收它的資源后就會消亡,或者在父進程退出后,由 init 進程回收后也會消亡
- 一旦父進程沒有處理子進程的終止,還一直保持運行狀態,那么子進程就會一直處於僵屍狀態
- 大量的僵屍進程會用盡 PID 進程號,導致新進程不能創建
大量不可中斷狀態和僵屍狀態進程的案例
系統配置
- Ubuntu 18.04, 2 CPU,2GB 內存
- 前置條件:已運行案例應用
通過 ps 命令查看案例進程
ps aux | grep /app
結果分析
- 多個 app 進程已啟動
- 狀態有 Ss+、D+、R+
- 小s:表示這個進程是一個會話的領導進程
- +:表示前台進程組
什么是會話和進程組
- 它們是用來管理一組相互關聯的進程
- 進程組:比如每個子進程都是父進程所在組的成員
- 會話:共享同一個控制終端的一個或多個進程組
會話和進程組的場景類比
- 通過 SSH 登錄服務器,就會打開一個控制終端(TTY),這個控制終端就對應 一個會話
- 而在終端中運行的命令以及它們的子進程,就構成了一個個的進程組
- 在后台運行的命令,構成后台進程組
- 在前台運行的命令,構成前台進程組
通過 top 查看系統狀況
結果分析
- 平均負載,過去 1min、5min、15min 的平均負載依次減少,說明平均負載正在升高
- 而 1min 內的平均負載已經達到系統 CPU 個數,說明系統很可能存在性能瓶頸
- 115 zombie 說明僵屍進程比較多,而且在不停增加,有子進程在退出時沒被清理
- 用戶 CPU 和系統 CPU 都不高,但 iowait 分別是 60.5% 和 94.6%,好像有點兒不正常,導致系統的平均負載升高
- 有兩個處於 D 狀態的 app 進程,可能在等待 I/O
查看系統的僵屍進程
ps -e -o stat,ppid,pid,cmd | egrep '^[Zz]' 或 ps -ef | grep "defunct"
一堆 app 僵屍進程
iowait 分析
一提到 iowait 升高,首先會想要查詢系統的 I/O 情況
運行 dstat 命令,觀察 CPU 和 I/O 的使用情況
dstat 1 10
- 當 iowait 升高(wai)時,磁盤的讀請求(read)都會很大(M)
- 這說明 iowait 的升高跟磁盤的讀請求有關,很可能就是讀磁盤導致的
找到讀磁盤的進程
- 通過 top 找到 D 狀態的兩個 app 進程
- 不可中斷狀態代表進程在跟硬件進行交互,很可能就是讀磁盤
兩個 app 進程的 PID 分別是12407、12406
通過 pidstat 查看 app 進程的 I/O 情況
pidstat -d -p 12407 1 5
- -d 展示 I/O 統計數據
- -p 指定進程號
- 間隔 1 秒輸出 5 組數據
- kB_rd 表示每秒讀的 KB 數, kB_wr 表示每秒寫的 KB 數,iodelay 表示 I/O 的延遲(單位是時鍾周期)
- 它們都是 0,那就表示此時沒有任何的讀寫,說明問題不 是 12407 進程導致的,也並不是12406 進程導致的
通過 pidstat 查看系統的 I/O 情況
pidstat -d 1 10
- 能看到其實的確是 app 進程在讀,只不過每過幾秒都會有新的 app 進程在讀【pid 在不斷變化】
- 可以確認,是 app 進程的問題
通過 ps 命令查看一直變化的 app 進程狀態
前面講到讀磁盤的 app 進程 PID 一直在變化,那么就來看看已經沒在讀磁盤的進程的進程狀態是怎么樣的
ps aux | grep 15973
- 這進程已經是 Z 狀態,就是僵屍進程了
- 僵屍進程都是已經退出的進程, 所以就沒法兒繼續分析它的系統調用
- 關於僵屍進程的處理方法,我們一會兒再說,現在還是繼續分析 iowait 的問題
通過 perf 錄制性能事件
- 系統 iowait 的問題還在繼續,但是 top、pidstat 這類工具已經不能給出更多的信息了
- 此時可以通過 perf 動態跟蹤性能事件
perf record -g
15s 后 ctrl+c 終止錄制
查看報告,分析報告
perf report
- app 的確在通過系統調用 sys_read() 讀取數據
- 並且從 new_sync_read 和 blkdev_direct_IO 能看出,進程正在對磁盤進行直接讀,也就是繞過了系統緩存,每個讀請求都會從磁盤直接讀,這就可以解釋觀察到的 iowait 升高了
修復源碼之后,通過 top 命令驗證
- iowait 已經非常低了,只有 0.3%
- 說明修改源碼已經成功修復了 iowait 高的問題
- 不過,仔細觀察僵屍進程的數量,會發現,僵屍進程還在不斷的增長中
處理和分析僵屍進程
- 僵屍進程是因為父進程沒有回收子進程的資源而出現的
- 解決僵屍進程需要先找出父進程,然后在父進程里解決
通過 pstree 找到某個 app 進程的父進程
pstree -aps 51780
51780 進程的父進程是 51688,也就是 app 應用
通過 ps 查看所有僵屍進程的父進程
ps -e -o stat,ppid,pid,cmd | egrep '^[Zz]'
所有僵屍進程的父進程都是 51688,從而確認 51688 就是僵屍進程的父進程
查看 app 應用程序的代碼
查看 app 應用程序的代碼,看看子進程結束的處理是否正確
- 有沒有調用 wait() 或 waitpid()
- 或有沒有注冊 SIGCHLD 信號的處理函數
把 wait() 放到了 for 死循環的外面,也就是說, wait() 函數實際上並沒被調用到,把它挪到 for 循環的里面就可以了
改完源碼,通過 top 驗證一下
僵屍進程(Z 狀態)沒有了, iowait 也是 0,問題終於全部解決了
總結
- 這個案例是因為磁盤 I/O 導致了 iowait 升高
- 不過,iowait 高並不一定代表 I/O 有性能瓶頸
- 當系統中只有 I/O 類型的進程在運行時,iowait 也會很高,但實際上,磁盤的讀寫遠沒有達到性能瓶頸的程度
分析整體思路
- 通過 top 查看系統資源情況
- 發現平均負載逐漸升高,iowait(wa)比較高,但用戶態和內核態 CPU 使用率並不算高
- 查看是否有 CPU 使用率偏高的進程,發現有 D 狀態的進程,可能是在等待 I/O 中
- 過一陣子會變成 Z 狀態進程,且 CPU 使用率上升,然后會看到 zombie 進程數逐漸增加
- 可以得到兩個結論:僵屍進程過多,應該是父進程沒有清理已經結束的子進程的資源;iowait 的上升導系統平均負載上升
- 因為是 iowait 較高,可以通過 dstat 查看系統的 I/O 情況,會發現每次 iowait 升高,讀磁盤請求都會很大
- 通過 pidstat -d 查看 D 狀態進程的 I/O 情況,但發現並沒有有效信息
- 通過 pidstat -d 直接查看系統的 I/O 情況,可以發現不斷有新進程在進行讀磁盤操作
- 通過 ps 命令查看剛剛 D 狀態進程當前的進程狀態,發現已經變成僵屍進程
- 通過 perf record 錄制性能事件,然后通過 perf report 查看性能報告,可以發現 app 進程都是直接讀磁盤,而不經過系統緩存
- 通過 pstree 找到 Z 狀態進程的父進程
- 通過 ps 命令確認所有僵屍進程的父進程
- 找到父進程源代碼,檢查 wait() / waitpid() 的是否會成功調用,或是 SIGCHLD 信號處理函數的注冊就行了
- 修改完全部源碼后,重新運行應用,通過 top 驗證是否還有 iowait 過高和出現 zombie 進程的情況