Linux效能優化實戰(二)

MXC肖某某發表於2022-02-17

一、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應用導致的殭屍程式。進一步檢視並修復。

    

  

相關文章