性能分析(3)- 短時進程導致用戶 CPU 使用率過高案例


性能分析小案例系列,可以通過下面鏈接查看哦

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 升高等問 題

 

分析整體思路

  1. 短時間壓測,發現服務器性能低下
  2. 長時間壓測,讓服務器保持一個高負載的狀態,從而可以慢慢分析問題所在
  3. 通過 top 命令監控系統資源情況,發現用戶態的 CPU 使用率(us)較高,且空閑 CPU (id) 很低
  4. 但是找不到用戶態 的 CPU 使用率很高的進程,最高就 6%
  5. 進一步通過 pidstat 查看是否有 CPU 使用率異常高的進程
  6. 發現 pidstat 行不通,再次通過 top 命令仔細觀察
  7. 發現 Running(R)狀態的進程有 6 個之多,但是 CPU 使用率較高的進程並沒有處於 R 狀態
  8. 切換 top 列表的排序規則,倒序,集中看 R 狀態的進程一段時間,發現是 stress 進程,而且時多時少,進程 PID 還會變化
  9. 通過 watch 實時觀察 stress 進程,發現 stress 進程會從從 R 狀態變到 S 再變到 Z,而且會不斷生成新的 stress 進程
  10. 猜測 stress 進程可能是短時進程,通過其他進程進行調用它
  11. 通過 pstree 找到 stress 進程的父進程,發現是 php-fpm 進程,接下來就是分析父進程了
  12. 通過 grep 查找 stress 命令是否存在 Nginx 應用中,發現存在 index.php 文件中
  13. 查看源碼,確認每次請求都會觸發 stress 命令
  14. stress 命令可以模擬 I/O 壓力的,通過 top 看到 iowait 其實並不算高
  15. 在 VM2 發送帶請求參數,可以確認 stress 命令是執行失敗的
  16. 可以猜測大量的請求進來,導致大量的 stress 進程初始化執行失敗,從而增加進程上下文切換次數增加,最終導致 CPU 使用率升高
  17. 通過 vmstat 對比壓測前后的上下文切換次數,可以發現壓測的上下文切換次數的確增加了
  18. 通過 pidstat 查看 stress 的上下文切換次數,但發現並不高,其實是因為 stress 是短時進程,上下文切換次數主要增加在舊進程切換到新進程的運行上
  19. 通過 perf record 錄制性能事件 30s
  20. 通過 perf report 查看性能報告,可以看到占用 CPU 時鍾事件的前幾名都是 stress 進程,然后調度比例最高的是 random 函數
  21. 確認問題根源就是 stress 進程調用了 random 函數

 

炒雞重點

  • 其實有時候 top、vmstat、pidstat 命令用完了可能還不一定能發現問題根源
  • 這個時候就要更加細心的查看這些命令返回結果其中的貓膩,雖然可能找不到問題根源,但可能會發現某些線索
  • perf 雖然強大,但並不是一開始分析就適合用它的

 


免責聲明!

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



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