1. 程式人生 > 實用技巧 >效能分析(4)- iowait 使用率過高案例

效能分析(4)- iowait 使用率過高案例

效能分析小案例系列,可以通過下面連結檢視哦

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_readblkdev_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 應用程式的程式碼,看看子程序結束的處理是否正確

  1. 有沒有呼叫wait()waitpid()
  2. 或有沒有註冊 SIGCHLD 訊號的處理函式

wait()放到了 for 死迴圈的外面,也就是說,wait() 函式實際上並沒被呼叫到,把它挪到 for 迴圈的裡面就可以了

改完原始碼,通過 top 驗證一下

殭屍程序(Z 狀態)沒有了, iowait 也是 0,問題終於全部解決了

總結

  • 這個案例是因為磁碟 I/O 導致了 iowait 升高
  • 不過,iowait 高並不一定代表 I/O 有效能瓶頸
  • 當系統中只有 I/O 型別的程序在執行時,iowait 也會很高,但實際上,磁碟的讀寫遠沒有達到效能瓶頸的程度

分析整體思路

  1. 通過 top 檢視系統資源情況
  2. 發現平均負載逐漸升高,iowait(wa)比較高,但使用者態和核心態 CPU 使用率並不算高
  3. 檢視是否有 CPU 使用率偏高的程序,發現有 D 狀態的程序,可能是在等待 I/O 中
  4. 過一陣子會變成 Z 狀態程序,且 CPU 使用率上升,然後會看到zombie 程序數逐漸增加
  5. 可以得到兩個結論:殭屍程序過多,應該是父程序沒有清理已經結束的子程序的資源;iowait 的上升導系統平均負載上升
  6. 因為是 iowait 較高,可以通過 dstat 檢視系統的 I/O 情況,會發現每次 iowait 升高,讀磁碟請求都會很大
  7. 通過 pidstat -d 檢視 D 狀態程序的 I/O 情況,但發現並沒有有效資訊
  8. 通過 pidstat -d 直接檢視系統的 I/O 情況,可以發現不斷有新程序在進行讀磁碟操作
  9. 通過 ps 命令檢視剛剛 D 狀態程序當前的程序狀態,發現已經變成殭屍程序
  10. 通過 perf record 錄製效能事件,然後通過 perf report 檢視效能報告,可以發現 app 程序都是直接讀磁碟,而不經過系統快取
  11. 通過 pstree 找到 Z 狀態程序的父程序
  12. 通過 ps 命令確認所有殭屍程序的父程序
  13. 找到父程序原始碼,檢查 wait() / waitpid() 的是否會成功呼叫,或是 SIGCHLD 訊號處理函式的註冊就行了
  14. 修改完全部原始碼後,重新執行應用,通過 top 驗證是否還有 iowait 過高和出現 zombie 程序的情況