講解 Linux Load 高如何排查的話題屬于老生常談了,但多數文章隻是聚焦了幾個點,缺少整體排查思路的介紹。所謂 “授人以魚不如授人以漁”。本文試圖建立一個方法和套路,來幫助讀者對 Load 高問題排查有一個更全面的認識。
從消除誤解開始
沒有基線的 Load,是不靠譜的 Load
從接觸 Unix/Linux 系統管理的第一天起,很多人就開始接觸
System Load Average
這個監控名額了,然而,并非所有人都知道這個名額的真正含義。一般說來,經常能聽到以下誤解:
-
Load 高是 CPU 負載高……
傳統 Unix 于 Linux 設計不同。Unix 系統,Load 高就是可運作程序多引發的,但對 Linux 來說不是。對 Linux 來說 Load 高可能有兩種情況:
-
- 系統中處于
狀态的程序數增加引發的R
-
D
- 系統中處于
-
Loadavg 數值大于某個值就一定有問題……
Loadavg 的數值是相對值,受到 CPU 和 IO 裝置多少的影響,甚至會受到某些軟體定義的虛拟資源的影響。Load 高的判斷需要基于某個曆史基線 (Baseline),不能無原則的跨系統去比較 Load。
-
Load 高系統一定很忙…..
Load 高系統可以很忙,例如 CPU 負載高,CPU 很忙。但 Load 高,系統不都很忙,如 IO 負載高,磁盤可以很忙,但 CPU 可以比較空閑,如 iowait 高。這裡要注意,iowait 本質上是一種特殊的 CPU 空閑狀态。另一種 Load 高,可能 CPU 和磁盤外設都很空閑,可能支援鎖競争引起的,這時候 CPU 時間裡,iowait 不高,但 idle 高。
Brendan Gregg 在最近的部落格 [Linux Load Averages: Solving the Mystery] (http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html) 中,讨論了 Unix 和 Linux Load Average 的差異,并且回朔到 24 年前 Linux 社群的讨論,并找到了當時為什麼 Linux 要修改 Unix Load Average 的定義。文章認為,正是由于 Linux 引入的
D
狀态線程的計算方式,進而導緻 Load 高的原因變得含混起來。因為系統中引發
D
狀态切換的原因實在是太多了,絕非 IO 負載,鎖競争這麼簡單!正是由于這種含混,Load 的數值更加難以跨系統,跨應用類型去比較。所有 Load 高低的依據,全都應該基于曆史的基線。本微信公衆号也曾寫過一篇相關文章,可以參見
Linux Load Average那些事兒。

如何排查 Load 高的問題
如前所述,由于在 Linux 作業系統裡,Load 是一個定義及其含混的名額,排查 loadavg 高就是一個很複雜的過程。其基本思路就是,根據引起 Load 變化的根源是
R
狀态任務增多,還是
D
狀态任務增多,來進入到不同的流程。
這裡給出了 Load 增高的排查的一般套路,僅供參考:
在 Linux 系統裡,讀取
/proc/stat
檔案,即可擷取系統中
R
狀态的程序數;但
D
狀态的任務數恐怕最直接的方式還是使用
ps
指令比較友善。而
/proc/stat
檔案裡
procs_blocked
則給出的是處于等待磁盤 IO 的程序數:
通過簡單區分
R
狀态任務增多,還是
D
狀态任務增多,我們就可以進入到不同的排查流程裡。下面,我們就這個大圖的排查思路,做一個簡單的梳理。
R
狀态任務增多
R
即通常所說的 CPU 負載高。此類問題的排查定位主要思路是系統,容器,程序的運作時間分析上,找到在 CPU 上的熱點路徑,或者分析 CPU 的運作時間主要是在哪段代碼上。
CPU
user
和
sys
時間的分布通常能幫助人們快速定位與使用者态程序有關,還是與核心有關。另外,CPU 的 run queue 長度和排程等待時間,非主動的上下文切換 (nonvoluntary context switch) 次數都能幫助大緻了解問題的場景。
是以,如果要将問題的場景關聯到相關的代碼,通常需要使用
perf
,
systemtap
,
ftrace
這種動态的跟蹤工具。
關聯到代碼路徑後,接下來的代碼時間分析過程中,代碼中的一些無效的運作時間也是分析中首要關注的,例如使用者态和核心态中的自旋鎖 (Spin Lock)。
當然,如果 CPU 上運作的都是有非常意義,非常有效率的代碼,那唯一要考慮的就是,是不是負載真得太大了。
D
D
根據 Linux 核心的設計,
D
狀态任務本質上是
TASK_UNINTERRUPTIBLE
引發的主動睡眠,是以其可能性非常多。但是由于 Linux 核心 CPU 空閑時間上對 IO 棧引發的睡眠做了特殊的定義,即
iowait
,是以
iowait
成為
D
狀态分類裡定位是否 Load 高是由 IO 引發的一個重要參考。
當然,如前所述,
/proc/stat
中的
procs_blocked
的變化趨勢也可以是一個非常好的判定因
iowait
引發的 Load 高的一個參考。
iowait
高
iowait
很多人通常都對 CPU
iowait
有一個誤解,以為
iowait
高是因為這時的 CPU 正在忙于做 IO 操作。其實恰恰相反,
iowait
高的時候,CPU 正處于空閑狀态,沒有任何任務可以運作。隻是因為此時存在已經發出的磁盤 IO,是以這時的空閑狀态被辨別成了
iowait
,而不是
idle
但此時,如果用
perf probe
指令,我們可以清楚得看到,在
iowait
狀态的 CPU,實際上是運作在 pid 為 0 的 idle 線程上:
相關的 idle 線程的循環如何分别對 CPU
iowait
和
idle
計數的代碼,如下所示:
而 Linux IO 棧和檔案系統的代碼則會調用
io_schedule
,等待磁盤 IO 的完成。這時候,對 CPU 時間被記為
iowait
起關鍵計數的原子變量
rq->nr_iowait
則會在睡眠前被增加。注意,io_schedule 在被調用前,通常 caller 會先将任務顯式地設定成
TASK_UNINTERRUPTIBLE
狀态:
CPU idle
高
idle
如前所述,有相當多的核心的阻塞,即
TASK_UNINTERRUPTIBLE
的睡眠,實際上與等待磁盤 IO 無關,如核心中的鎖競争,再如記憶體直接頁回收的睡眠,又如核心中一些代碼路徑上的主動阻塞,等待資源。
Brendan Gregg 在最近的部落格 [Linux Load Averages: Solving the Mystery] (http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html)中,使用
perf
指令産生的
TASK_UNINTERRUPTIBLE
的睡眠的火焰圖,很好的展示了引起 CPU
idle
高的多樣性。本文不在贅述。
是以,CPU
idle
高的分析,實質上就是分析核心的代碼路徑引起阻塞的主因是什麼。通常,我們可以使用
perf inject
對
perf record
記錄的上下文切換的事件進行處理,關聯出程序從 CPU 切出 (swtich out) 和再次切入 (switch in) 的核心代碼路徑,生成一個所謂的 Off CPU 火焰圖.
當然,類似于鎖競争這樣的比較簡單的問題,Off CPU 火焰圖足以一步定位出問題。但是對于更加複雜的因
D
狀态而阻塞的延遲問題,可能 Off CPU 火焰圖隻能給我們一個調查的起點。
例如,當我們看到,Off CPU 火焰圖的主要睡眠時間是因為
epoll_wait
等待引發的。那麼,我們繼續要排查的應該是網絡棧的延遲,即本文大圖中的 Net Delay 這部分。
至此,你也許會發現,CPU
iowait
idle
高的性能分析的實質就是
延遲分析
。這就是大圖按照核心中資源管理的大方向,将延遲分析細化成了六大延遲分析:
- CPU 延遲
- 記憶體延遲
- 檔案系統延遲
- IO 棧延遲
- 網絡棧延遲
- 鎖及同步原語競争
任何上述代碼路徑引發的
TASK_UNINTERRUPTIBLE
的睡眠,都是我們要分析的對象!
以問題結束
限于篇幅,本文很難将其所涉及的細節一一展開,因為讀到這裡,你也許會發現,原來 Load 高的分析,實際上就是對系統的全面負載分析。怪不得叫 System Load 呢。這也是 Load 分析為什麼很難在一篇文章裡去全面覆寫。
本文也開啟了淺談 Linux 性能分析系列的第一章。後續我們會推出系列文章,就前文所述的六大延遲分析,一一展開介紹,敬請期待……
關于作者
楊勇 (Oliver Yang),Linux 核心工程師,來自阿裡雲系統組。曾就職于 EMC,Sun 中國工程研究院,在存儲系統和 Solaris 核心開發領域工作。
近幾年來開始研究 Linux 核心,對 Linux 性能優化,記憶體管理子系統有濃厚興趣。
他的聯系方式:
http://oliveryang.net/about