Linux性能優化實戰CPU篇(二)


一、CPU使用率過高

1,CPU使用率

a>節拍率

  為了維護CPU時間,Linux通過事先定義的節拍率(內核中表示為HZ),觸發時間中斷,並使用全局變量Jiffies記錄開機以來的節拍數。每發生一次時間中斷,Jiffies的值就加1

  節拍率HZ是內核的可配置選項

#查看當前系統的節拍率為每秒鍾250次時間中斷
grep 'CONFIG_HZ=' /boot/config-$(uname -r)
CONFIG_HZ=250

  同時內核還提供了一個用戶空間節拍率USER_HZ,固定值為100,也就是1/100秒

b>/proc虛擬文件系統

cpu 2032004 102648 238344 167130733 758440 15159 17878 0
cpu0 1022597 63462 141826 83528451 366530 9362 15386 0
cpu1 1009407 39185 96518 83602282 391909 5796 2492 0
intr 303194010 212852371 3 0 0 11 0 0 2 1 1 0 0 3 0 11097365 0 72615114 6628960 0 179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 236095529
btime 1195210746
processes 401389
procs_running 1
procs_blocked 0

  第一行的數值表示的是CPU總的使用情況,所以我們只要用第一行的數字計算就可以了。下表解析第一行各數值的含義:

參數                    解析(單位:jiffies)
user(2032004)          從系統啟動開始累計到當前時刻,用戶態的CPU時間,不包含 nice值為負進程。
nice(102648)          從系統啟動開始累計到當前時刻,nice值為負的進程所占用的CPU時間
system (238344)        從系統啟動開始累計到當前時刻,核心時間
idle (167130733)       從系統啟動開始累計到當前時刻,除IO等待時間以外其它等待時間
iowait (758440)        從系統啟動開始累計到當前時刻,IO等待時間
irq (15159)            從系統啟動開始累計到當前時刻,硬中斷時間
softirq (17878)        從系統啟動開始累計到當前時刻,軟中斷時間

c>CPU使用率

  CPU在t1到t2時間段即時利用率 =  1 - CPU空閑使用時間 / CPU總的使用時間

  CPU在t1到t2時間段空閑使用時間 = (idle2 - idle1)

  CPU在t1到t2時間段總的使用時間 = ( user2+ nice2+ system2+ idle2+ iowait2+ irq2+ softirq2) - ( user1+ nice1+ system1+ idle1+ iowait1+ irq1+ softirq1)

2,如何查看CPU使用率

a>top

  top默認使用3秒時間間隔,它顯示了系統總體的CPU和內存使用情況,以及各個進程的資源使用情況

top - 09:52:06 up 2 days, 53 min,  1 user,  load average: 0.38, 0.69, 0.88
任務: 445 total,   1 running, 353 sleeping,   0 stopped,   4 zombie
%Cpu(s):  2.3 us,  1.4 sy,  0.0 ni, 95.8 id,  0.0 wa,  0.0 hi,  0.5 si,  0.0 st
KiB Mem : 16163124 total,   588480 free, 11530672 used,  4043972 buff/cache
KiB Swap:  2097148 total,  1243900 free,   853248 used.  2415292 avail Mem 
PID USER      PR  NI    VIRT    RES    SHR �  %CPU %MEM     TIME+ COMMAND                                                                                                                                 
11775 mi        20   0 4872384 499200 134812 S  11.5  3.1  42:41.12 gnome-shell                                                                                                                             
11318 mi        20   0 1355336 261508 226128 S   7.6  1.6  53:12.32 Xorg                                                                                                                                    
21215 mi        20   0 3407932 604084  27340 S   6.6  3.7 135:50.48 WeChat.exe                                                                                                                              
21220 mi        20   0   10068   6080    684 S   6.2  0.0 120:01.52 wineserver.real                                                                                                                         
 9586 mi        20   0  693000  36112  21156 S   4.6  0.2   0:19.49 gnome-terminal-                                                                                                                         
 5907 mi        20   0 13.246g 3.962g  29032 S   3.9 25.7 632:20.02 java 

  第三行%Cpu就是系統的CPU使用率,具體每列的含義與/proc類似

b>ps

   ps (英文全拼:process status)命令用於顯示當前進程的狀態,進程的整個生命周期

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0 225968  8288 ?        Ss   2月07   1:57 /lib/systemd/systemd --system --deserialize 19
root         2  0.0  0.0      0     0 ?        S    2月07   0:00 [kthreadd]

  具體含義

USER: 行程擁有者
PID: pid
%CPU: 占用的 CPU 使用率
%MEM: 占用的記憶體使用率
VSZ: 占用的虛擬記憶體大小
RSS: 占用的記憶體大小
TTY: 終端的次要裝置號碼 (minor device number of tty)
STAT: 該行程的狀態:D: 無法中斷的休眠狀態 (通常 IO 的進程); R: 正在執行中; S: 靜止狀態; T: 暫停執行; Z: 不存在但暫時無法消除; W: 沒有足夠的記憶體分頁可分配; <: 高優先序的行程; N: 低優先序的行程; L: 有記憶體分頁分配並鎖在記憶體內 (實時系統或捱A I/O)
START: 行程開始時間
TIME: 執行的時間
COMMAND:所執行的指令

c>pidstat

pidstat [ 選項 ] [ <時間間隔> ] [ <次數> ]

  可以指定時間間隔來輸出CPU使用率

#每隔 1 秒輸出一組數據,共輸出 5 組
mi@mi:~$ pidstat 1 5
10時08分51秒   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
10時08分52秒  1000       641    0.99    0.00    0.00    0.00    0.99     9  chrome
10時08分52秒  1000       996    0.00    0.99    0.00    0.00    0.99     4  chrome
10時08分52秒  1000      4387    0.99    0.99    0.00    0.00    1.98     0  pidstat
10時08分52秒  1000      5907    3.96    0.00    0.00    0.00    3.96     2  java
...
平均時間: UID PID %usr %system %guest %wait %CPU CPU Command
平均時間: 0 11 0.00 0.20 0.00 0.00 0.20 - rcu_sched
平均時間: 1000 548 0.20 0.20 0.00 0.00 0.40 - chrome
平均時間: 0 1745 0.00 0.20 0.00 0.00 0.20 - kworker/u24:4-events_unbound
平均時間: 0 2568 0.00 0.20 0.00 0.00 0.20 - kworker/u24:2-i915
平均時間: 0 3545 0.00 0.20 0.00 0.00 0.20 - kworker/9:2-events
  • %usr 用戶態CPU使用率

  • %system 內核態CPU使用率

  • %guest 運行虛擬機CPU使用率

  • %wait 等待CPU使用率

  • %CPU 總的CPU使用率

  • 最后的Average,則為計算了5組數據的平均值

3,CPU使用率過高怎么辦?

  perf 火焰圖

a>采用top、ps、pidstat等工具,找到CPU使用率較高的進程

b>使用perf top 展示熱點函數

  

  輸出結果中,第一行包含三組數據,分別是采樣數(Samples)、事件類型(event)和事件總數量(Event count)。比如當前perf采集了833個CPU時鍾事件,總事件數為97742399。

  采樣數需要盡可能的多,這樣才有參考價值。解析表格樣式的數據為:

  • Overhead,是該符號的性能事件在所有采樣中的比例,用百分比來表示
  • Shared,是該函數或指令所在的動態共享對象(Dynamic Shared Object),如內核、進程名、動態鏈接庫名、內核模塊名等。
  • Object,是動態共享對象的類型。比如 [.] 表示用戶空間的可執行程序、或者動態鏈接庫,而  [k] 則表示內核空間
  • Symbol,函數名。當函數名未知時,用十六進制的地址來表示

  從上圖可以看出CPU時鍾最多的是perf工具自身,其比例也只有7.28%,說明系統並沒有CPU性能問題。

c>perf record 和 perf report

  perf top 雖然實時展示了系統的性能信息,但他的缺點是並不保存數據,無法用於離線或后續的分析

  perf record 則提供了保存數據的功能,保存后的數據,需要用perf report解析展示

//按 ctrl + c 終止采樣
root@xc:~# perf record
 [ perf record: Woken up 1 times to write data ]
 [ perf record: Captured and wrote 3.679 MB perf.data (26683 samples) ]
//展示類似於 perf top的報告
root@xc:~# perf report

  在實際使用中,我們經常使用perf top 和 perf record 加上 -g 參數,開啟調用關系的采樣,方便調用鏈的分析

4,案例一

  使用兩台虛擬機,其中一台用作Web服務器,來模擬性能問題;另一台用作Web服務器的客戶端,來給Web服務器增加壓力請求。使用兩台虛擬機是為了相互隔離,避免“交叉感染”。

  

VM1上啟動應用

  

VM2上測試並壓測

  

  啟動成功,進行性能壓測

  

  從ab輸出結果可以看到,Nginx能承受的每秒平均請求數只有11.63。為了方便分析,我們將請求數增到10000,繼續壓測

  

VM1上分析原因

  top,可以看到php-fpm的CPU使用率加起來將近200%;每個CPU的使用率均已超過98%

  

  perf top ,使用-g 開啟調用關系分析, -p 指定 php-fpm的進程號 21515

  

  發現調用關系最終到sqrt和add_function出現占用的CPU時鍾較高。

  

5,案例二(定位CPU升高的應用)

  使用兩台虛擬機,其中一台用作Web服務器,來模擬性能問題;另一台用作Web服務器的客戶端

   

VM1上啟動應用

  

VM2上測試並壓測:

   

   啟動成功,進行壓測

  

  從ab結果可以看到,Nginx能承受的每秒平均請求數,只有87左右。接下來,將並發請求數改成5,延長請求時長為10分鍾,方便vm1定位分析問題

  

VM1上分析原因

  top分析,發現CPU使用最高的進程才2.7%,並不特別高。然而在CPU使用率(%Cpu),可以看到用戶CPU使用率為80%,系統CPU為15.1%,而空閑CPU為2.8%

   

  pidstat,觀察發現CPU的使用率也都不高

  

   

  再次采用top觀察,發現Tasks中有個6個Running狀態的進程,而觀察咱們啟動的php-fpm確都處於Sleep(S)狀態,真正處於Running狀態的時stress進程

                  

   采用pidstat進一步觀察其中的一個stress進程,發現無輸出

  

  采用ps查看這個stress進程,任然無輸出。

   

  再次采用top命令查看,發現stress進程的進程號都已經發生變化

  

  分析原因

  • 進程在不斷地崩潰重啟,比如因為配置錯誤等,進程在退出后可能又被監控系統自動重啟
  • 進程為短時進程,也就是在其他應用內部通過exec調用的外部命令。這些命令一般都只運行很短的時間就會結束,也很難通過top這種間隔時間較長的工具發現

  通過pstree查找真實的stress進程的父進程,這里可以看到stress是被php-fpm調用的子進程,並且進程數量不止一個(這里是3個)

  

   采用perf record -g命令,等待一會兒之后按Ctrl+C退出,再用perf report查看報告

  

                   

  發現 stress占用了CPU時鍾事件的77%,而stress調用棧中比例較高的為函數random(),再進行具體的優化,就可以解決。

6,execsnoop                     

  execsnoop-專門用於為追蹤短時進程(瞬時進程)設計的工具;它通過 ftrace 實時監控進程的 exec() 行為,並輸出短時進程的基本信息,包括進程 PID、父進程 PID、命令行參數以及執行的結果。

  github地址: https://github.com/brendangregg/perf-tools/blob/master/execsnoop

  如何安裝使用:將上面的github的內容復制,然后寫入execsnoop文件,並且加上x權限即可;

  

   可以發現大量的stress進程在不停啟動

二、僵屍進程      

1,進程的狀態

  以top為例,在top中有一列為S列(也就是Status列),可以查看到有T、D、Z、S、I等幾個狀態

  • R是Running或Runnable的縮寫,表示進程在CPU的就緒隊列中,正在運行或者正在等待運行
  • D是Disk Sleep的縮寫,也就是不可中斷狀態睡眠(Uninterruptible Sleep),一般表示進程正在跟硬件交互,並且交互過程不允許被其他進程中斷
  • Z是Zombie的縮寫,它表示僵屍進程,也就是進程實際上已經結束了,但是父進程還沒有回收它的資源(比如進程的描述符、PID等)
  • S是Interruptible Sleep的縮寫,也就是可中斷狀態睡眠,表示進程因為等待某個事件而被系統掛起。當進程等待的事件發生時,它會被喚醒並進入R狀態
  • I是Idle的縮寫,也就是空閑狀態,用在不可中斷睡眠的內核線程上。前面說了,硬件交互導致的不可中斷進程用D表示,但對某些內核線程來說,它們有可能實際上並沒有任何負載,用Idle正是為了區分這種情況。因為,D狀態的進程會導致平均負載升高,I狀態的進程卻不會
  • T(t)是Stopped或Traced的縮寫,表示進程處於暫停或跟蹤狀態。
  • X是Dead的縮寫,表示進程已經消亡,不能在top或ps中看到

  當一個進程創建了子進程后,它應該通過系統調用wait()或waitpid()等待子進程結束,回收子進程的資源;而子進程在結束時,會向它的父進程發送SIGCHLD信號,所以父進程也可以注冊SIGCHLD信號的處理函數,異步回收資源。

  如果父進程沒這么做,或子進程執行太快,父進程還沒有來得及處理子進程狀態,子進程就已經提前退出,那么這時的子進程就會變成僵屍進程。通常,僵屍進程持續的時間都比較短,在父進程回收它的資源后就會消亡;或者在父進程退出后,由init進程回收后也會消亡。

  一旦父進程沒有處理子進程的終止,還一直保持運行狀態,那么子進程就會處於僵屍狀態。大量的僵屍進程會用盡PID進程號,導致新進程不能創建,所以這種情況需要避免。

2,案例分析

  准備Ubuntu 18.04 機器配置 2CPU,8GB內存,預先安裝docker、sysstat、dstat等工具

啟動應用

  

ps查看應用為正常啟動

  

  Ss+和D+。其中S表示可中斷睡眠,D表示不可中斷睡眠。s表示這個進程是一個會話的領導進程,+表示前台進程組 

top查看

  

  • 第一行看平均負載(load average),過去1分鍾、5分鍾和15分鍾的平均負載在依次縮小,說明平均負載在升高。1分鍾內的平均負載為2,說明有可能已經達到性能瓶頸
  • 第二行看Tasks,有1個正在運行的進程,但是發現zombie僵屍進程比較多,而且在不停的增加,說明有子進程在退出時未被清理
  • 查看CPU的使用情況,發現用戶CPU和系統CPU都不高,但是iowait分別時60.5%和94.6% 

dstat分析iowait

   

   可以看到當iowait(wai)升高時,磁盤的讀請求(read)都會很大。這說明iowait的升高跟磁盤的讀請求有關,很可能就是磁盤讀導致的。接下來需要進一步分析具體的哪個進程讀磁盤呢?

pidstat采用-d分析I/O和進程之間的情況

  

   可以發現app進程每秒讀的數據為32MB,基本可以斷定是app應用導致。接下來繼續追蹤app進程在執行什么I/O操作?

strace追蹤進程6082

   

 ps查看6082進程情況,發現6082已經是僵屍進程。

  

目前iowait還在繼續,但是top、pidstat這類工具已經不能給予幫助了,接下來采用perf查看

  

   

  其中swapper是內核中的調度進程,可以先忽略。可以看到在app中通過系統調用sys_read()讀取數據。並且從new_sync_read和blkdev_read_iter能看出,進程正在對磁盤進行直接讀,也就是繞過了系統緩存,每個讀請求都會直接從磁盤讀取,導致iowait升高。 定位到原因后就可以打開源碼發現在app.c的文件出現了O_DIRECT選項直接打開磁盤,刪除這個選項

   

重啟后,再使用top查看,發現iowait已經很低了只有0.3%。但是查看僵屍進程的數量還是在不斷的增加

  

采用pstree定位父進程,發現還是app應用導致的僵屍進程。進一步查看並修復。

    

  


免責聲明!

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



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