1. 程式人生 > >07.應對系統中出現大量不可中斷程序和殭屍程序

07.應對系統中出現大量不可中斷程序和殭屍程序

上一篇,用一個 Nginx+PHP 的案例,給你講了伺服器 CPU 使用率高的分析和應對方法。這裡一定要記得,當碰到無法解釋的 CPU 使用率問題時,先要檢查一下是不是短時應用在搗 鬼。 短時應用的執行時間比較短,很難在 top 或者 ps 這類展示系統概要和程序快照的工具中發現, 你需要使用記錄事件的工具來配合診斷,比如 execsnoop 或者 perf top

這些思路你不用刻意去背,多練習幾次,多在操作中思考,你便能靈活運用。

另外,我們還講到 CPU 使用率的型別。除了上一節提到的使用者 CPU 之外,它還包括系統 CPU(比如上下文切換)、等待 I/O 的 CPU(比如等待磁碟的響應)以及中斷 CPU(包括軟中 斷和硬中斷)等。

我們已經在上下文切換的文章中,一起分析了系統 CPU 使用率高的問題,剩下的等待 I/O 的 CPU 使用率(以下簡稱為 iowait)升高,也是最常見的一個伺服器效能問題。

我們來看 一個多程序 I/O 的案例,並分析這種情況。

程序狀態

當 iowait 升高時,程序很可能因為得不到硬體的響應,而長時間處於不可中斷狀態。從 ps 或 者 top 命令的輸出中,你可以發現它們都處於 D 狀態,也就是不可中斷狀態(Uninterruptible Sleep)。既然說到了程序的狀態,程序有哪些狀態你還記得嗎?我們先來回顧一下。

top 和 ps 是最常用的檢視程序狀態的工具,我們就從 top 的輸出開始。下面是一個 top 命令 輸出的示例,S 列(也就是 Status 列)表示程序的狀態。從這個示例裡,你可以看到 R、D、 Z、S、I 等幾個狀態,它們分別是什麼意思呢?

$ top
 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
28961 root 20 0 43816 3148 4040 R 3.2 0.0 0:00.01 top
 620 root 20 0 37280 33676 908 D 0.3 0.4 0:00.01 app
 1 root 20 0 160072 9416 6752 S 0.0 0.1 0:37.64 systemd
 1896 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 devapp
 2 root 20 0 0 0 0 S 0.0 0.0 0:00.10 kthreadd
 4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
 
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq 7 root 20 0 0 0 0 S 0.0 0.0 0:06.37 ksoftirqd/0

我們挨個來看一下:

  • R 是 Running 或 Runnable 的縮寫,表示程序在 CPU 的就緒佇列中,正在執行或者正在等 待執行。
  • D 是 Disk Sleep 的縮寫,也就是不可中斷狀態睡眠(Uninterruptible Sleep),一般表示 程序正在跟硬體互動,並且互動過程不允許被其他程序或中斷打斷。
  • Z 是 Zombie 的縮寫,如果你玩過“植物大戰殭屍”這款遊戲,應該知道它的意思。它表示 殭屍程序,也就是程序實際上已經結束了,但是父程序還沒有回收它的資源(比如進 程的描 述符、PID 等)。
  • S 是 Interruptible Sleep 的縮寫,也就是可中斷狀態睡眠,表示程序因為等待某個事件而被 系統掛起。當程序等待的事件發生時,它會被喚醒並進入 R 狀態。
  • I 是 Idle 的縮寫,也就是空閒狀態,用在不可中斷睡眠的核心執行緒上。前面說了,硬體互動 導致的不可中斷程序用 D 表示,但對某些核心執行緒來說,它們有可能實際上並沒有任何負載,用 Idle 正是為了區分這種情況。要注意,D 狀態的程序會導致平均負載升高, I 狀態的 程序卻不會。

當然了,上面的示例並沒有包括程序的所有狀態。除了以上 5 個狀態,程序還包括下面這 2 個 狀態。

第一個是 T 或者 t,也就是 Stopped 或 Traced 的縮寫,表示程序處於暫停或者跟蹤狀態。

向一個程序傳送 SIGSTOP 訊號,它就會因響應這個訊號變成暫停狀態(Stopped);再向它發 送 SIGCONT 訊號,程序又會恢復執行(如果程序是終端裡直接啟動的,則需要你用 fg 命令, 恢復到前臺執行)。

而當你用偵錯程式(如 gdb)除錯一個程序時,在使用斷點中斷程序後,程序就會變成跟蹤狀 態,這其實也是一種特殊的暫停狀態,只不過你可以用偵錯程式來跟蹤並按需要控制程序的執行。

另一個是 X,也就是 Dead 的縮寫,表示程序已經消亡,所以你不會在 top 或者 ps 命令中看到 它。

瞭解了這些,我們再回到今天的主題。先看不可中斷狀態,這其實是為了保證程序資料與硬體狀 態一致,並且正常情況下,不可中斷狀態在很短時間內就會結束。所以,短時的不可中斷狀態進 程,我們一般可以忽略。

但如果系統或硬體發生了故障,程序可能會在不可中斷狀態保持很久,甚至導致系統中出現大量 不可中斷程序。這時,你就得注意下,系統是不是出現了 I/O 等效能問題。

再看殭屍程序,這是多程序應用很容易碰到的問題。正常情況下,當一個程序建立了子程序後, 它應該通過系統呼叫 wait() 或者 waitpid() 等待子程序結束,回收子程序的資源;而子程序在結 束時,會向它的父程序傳送 SIGCHLD 訊號,所以,父程序還可以註冊 SIGCHLD 訊號的處理函 數,非同步回收資源。

如果父程序沒這麼做,或是子程序執行太快,父程序還沒來得及處理子程序狀態,子程序就已經 提前退出,那這時的子程序就會變成殭屍程序。換句話說,父親應該一直對兒子負責,善始善 終,如果不作為或者跟不上,都會導致“問題少年”的出現。

通常,殭屍程序持續的時間都比較短,在父程序回收它的資源後就會消亡;或者在父程序退出 後,由 init 程序回收後也會消亡。

一旦父程序沒有處理子程序的終止,還一直保持執行狀態,那麼子程序就會一直處於殭屍狀態。 大量的殭屍程序會用盡 PID 程序號,導致新程序不能建立,所以這種情況一定要避免。

案例分析

接下來,將用一個多程序應用的案例,分析大量不可中斷狀態和殭屍狀態程序的問題。這 個應用基於 C 開發,由於它的編譯和執行步驟比較麻煩,把它打包成了一個 Docker 映象。 這樣,你只需要執行一個 Docker 容器就可以得到模擬環境。

你的準備

下面的案例仍然基於 Ubuntu 18.04,同樣適用於其他的 Linux 系統。我使用的案例環境如下所 示:

  • 機器配置:2 CPU,8GB 記憶體
  • 預先安裝 docker、sysstat、dstat 等工具,如 apt install docker.io dstat sysstat

這裡,dstat 是一個新的效能工具,它吸收了 vmstat、iostat、ifstat 等幾種工具的優點,可以同時觀察系統的 CPU、磁碟 I/O、網路以及記憶體使用情況。

接下來,我們開啟一個終端,SSH 登入到機器上,並安裝上述工具。

注意,以下所有命令都預設以 root 使用者執行,如果你用普通使用者身份登陸系統,請執行 sudo su root 命令切換到 root 使用者。 如果安裝過程有問題,你可以先上網搜尋解決,實在解決不了的,記得在留言區向我提問。

         溫馨提示:案例應用的核心程式碼邏輯比較簡單,你可能一眼就能看出問題,但實際 生產環境中的原始碼就複雜多了。所以,我依舊建議,操作之前別看原始碼,避免先入 為主,而           要把它當成一個黑盒來分析,這樣你可以更好地根據現象分析問題。你姑 且當成你工作中的一次演練,這樣效果更佳。

操作和分析

安裝完成後,我們首先執行下面的命令執行案例應用:

$ docker run --privileged --name=app -itd feisky/app:iowait

然後,輸入 ps 命令,確認案例應用已正常啟動。如果一切正常,你應該可以看到如下所示的輸 出:

$ ps aux | grep /app
root
4009 0.0 0.0 4376 1008 pts/0 Ss+ 05:51 0:00 /app root 4287 0.6 0.4 37280 33660 pts/0 D+ 05:54 0:00 /app root 4288 0.6 0.4 37280 33668 pts/0 D+ 05:54 0:00 /app

從這個介面,我們可以發現多個 app 程序已經啟動,並且它們的狀態分別是 Ss+ 和 D+。其 中,S 表示可中斷睡眠狀態,D 表示不可中斷睡眠狀態,我們在前面剛學過,那後面的 s 和 + 是什麼意思呢?不知道也沒關係,查一下 man ps 就可以。現在記住,s 表示這個程序是一個會 話的領導程序,而 + 表示前臺程序組。

這裡又出現了兩個新概念,程序組會話。它們用來管理一組相互關聯的程序,意思其實很好理 解。

  • 程序組表示一組相互關聯的程序,比如每個子程序都是父程序所在組的成員;
  • 而會話是指共享同一個控制終端的一個或多個程序組。

比如,我們通過 SSH 登入伺服器,就會開啟一個控制終端(TTY),這個控制終端就對應一個 會話。而我們在終端中執行的命令以及它們的子程序,就構成了一個個的程序組,其中,在後臺 執行的命令,構成後臺程序組;在前臺執行的命令,構成前臺程序組。

明白了這些,我們再用 top 看一下系統的資源使用情況:

# 按下數字 1 切換到所有 CPU 的使用情況,觀察一會兒按 Ctrl+C 結束
$ top
top - 05:56:23 up 17 days, 16:45, 2 users, load average: 2.00, 1.68, 1.39
Tasks: 247 total, 1 running, 79 sleeping, 0 stopped, 115 zombie
%Cpu0 : 0.0 us, 0.7 sy, 0.0 ni, 38.9 id, 60.5 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.7 sy, 0.0 ni, 4.7 id, 94.6 wa, 0.0 hi, 0.0 si, 0.0 st
...
 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
 4340 root 20 0 44676 4048 3432 R 0.3 0.0 0:00.05 top
 4345 root 20 0 37280 33624 860 D 0.3 0.0 0:00.01 app
 4344 root 20 0 37280 33624 860 D 0.3 0.4 0:00.01 app
 1 root 20 0 160072 9416 6752 S 0.0 0.1 0:38.59 systemd
...

從這裡你能看出什麼問題嗎?細心一點,逐行觀察,別放過任何一個地方。忘了哪行引數意思的 話,也要及時返回去複習。

好的,如果你已經有了答案,那就繼續往下走,看看跟我找的問題是否一樣。這裡,我發現了四 個可疑的地方。

  • 先看第一行的平均負載( Load Average),過去 1 分鐘、5 分鐘和 15 分鐘內的平均負載在 依次減小,說明平均負載正在升高;而 1 分鐘內的平均負載已經達到系統的 CPU 個數,說 明系統很可能已經有了效能瓶頸。
  • 再看第二行的 Tasks,有 1 個正在執行的程序,但殭屍程序比較多,而且還在不停增加,說 明有子程序在退出時沒被清理。
  • 接下來看兩個 CPU 的使用率情況,使用者 CPU 和系統 CPU 都不高,但 iowait 分別是 60.5% 和 94.6%,好像有點兒不正常。
  • 最後再看每個程序的情況, CPU 使用率最高的程序只有 0.3%,看起來並不高;但有兩個進 程處於 D 狀態,它們可能在等待 I/O,但光憑這裡並不能確定是它們導致了 iowait 升高

我們把這四個問題再彙總一下,就可以得到很明確的兩點:

第一點,iowait 太高了,導致系統的平均負載升高,甚至達到了系統 CPU 的個數。

第二點,殭屍程序在不斷增多,說明有程式沒能正確清理子程序的資源。

那麼,碰到這兩個問題該怎麼辦呢?結合我們前面分析問題的思路,你先自己想想,動手試試, 下節課我來繼續“分解”。

小結

今天我們主要通過簡單的操作,熟悉了幾個必備的程序狀態。用我們最熟悉的 ps 或者 top ,可 以檢視程序的狀態,這些狀態包括執行(R)、空閒(I)、不可中斷睡眠(D)、可中斷睡眠 (S)、殭屍(Z)以及暫停(T)等。

其中,不可中斷狀態和殭屍狀態,是我們今天學習的重點。

  • 不可中斷狀態,表示程序正在跟硬體互動,為了保護程序資料和硬體的一致性,系統不允許 其他程序或中斷打斷這個程序。程序長時間處於不可中斷狀態,通常表示系統有 I/O 效能問 題。
  • 殭屍程序表示程序已經退出,但它的父程序還沒有回收子程序佔用的資源。短暫的殭屍狀態 我們通常不必理會,但程序長時間處於殭屍狀態,就應該注意了,可能有應用程式沒有正常 處理子程序的退出。

思考

最後,思考課題,案例中發現的這兩個問題,會怎麼分析呢?又應該怎麼解決呢?