浅谈 Linux 高负载的系统化分析
簡(jiǎn)介:?淺談 Linux 高負(fù)載的系統(tǒng)化分析,阿里云系統(tǒng)組工程師楊勇通過(guò)對(duì)線上各種問(wèn)題的系統(tǒng)化分析。
講解 Linux Load 高如何排查的話題屬于老生常談了,但多數(shù)文章只是聚焦了幾個(gè)點(diǎn),缺少整體排查思路的介紹。所謂 “授人以魚不如授人以漁”。本文試圖建立一個(gè)方法和套路,來(lái)幫助讀者對(duì) Load 高問(wèn)題排查有一個(gè)更全面的認(rèn)識(shí)。
從消除誤解開始
沒(méi)有基線的 Load,是不靠譜的 Load
從接觸 Unix/Linux 系統(tǒng)管理的第一天起,很多人就開始接觸 System Load Average 這個(gè)監(jiān)控指標(biāo)了,然而,并非所有人都知道這個(gè)指標(biāo)的真正含義。一般說(shuō)來(lái),經(jīng)常能聽(tīng)到以下誤解:
- Load 高是 CPU 負(fù)載高……
傳統(tǒng) Unix 于 Linux 設(shè)計(jì)不同。Unix 系統(tǒng),Load 高就是可運(yùn)行進(jìn)程多引發(fā)的,但對(duì) Linux 來(lái)說(shuō)不是。對(duì) Linux 來(lái)說(shuō) Load 高可能有兩種情況: 
- 系統(tǒng)中處于 R 狀態(tài)的進(jìn)程數(shù)增加引發(fā)的
 - 系統(tǒng)中處于 D 狀態(tài)的進(jìn)程數(shù)增加引發(fā)的
 
- Loadavg 數(shù)值大于某個(gè)值就一定有問(wèn)題……
Loadavg 的數(shù)值是相對(duì)值,受到 CPU 和 IO 設(shè)備多少的影響,甚至?xí)艿侥承┸浖x的虛擬資源的影響。Load 高的判斷需要基于某個(gè)歷史基線 (Baseline),不能無(wú)原則的跨系統(tǒng)去比較 Load。 - Load 高系統(tǒng)一定很忙…..
Load 高系統(tǒng)可以很忙,例如 CPU 負(fù)載高,CPU 很忙。但 Load 高,系統(tǒng)不都很忙,如 IO 負(fù)載高,磁盤可以很忙,但 CPU 可以比較空閑,如 iowait 高。這里要注意,iowait 本質(zhì)上是一種特殊的 CPU 空閑狀態(tài)。另一種 Load 高,可能 CPU 和磁盤外設(shè)都很空閑,可能支持鎖競(jìng)爭(zhēng)引起的,這時(shí)候 CPU 時(shí)間里,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 社區(qū)的討論,并找到了當(dāng)時(shí)為什么 Linux 要修改 Unix Load Average 的定義。文章認(rèn)為,正是由于 Linux 引入的 D 狀態(tài)線程的計(jì)算方式,從而導(dǎo)致 Load 高的原因變得含混起來(lái)。因?yàn)橄到y(tǒng)中引發(fā) D 狀態(tài)切換的原因?qū)嵲谑翘嗔?#xff0c;絕非 IO 負(fù)載,鎖競(jìng)爭(zhēng)這么簡(jiǎn)單!正是由于這種含混,Load 的數(shù)值更加難以跨系統(tǒng),跨應(yīng)用類型去比較。所有 Load 高低的依據(jù),全都應(yīng)該基于歷史的基線。本微信公眾號(hào)也曾寫過(guò)一篇相關(guān)文章,可以參見(jiàn)Linux Load Average那些事兒。
如何排查 Load 高的問(wèn)題
如前所述,由于在 Linux 操作系統(tǒng)里,Load 是一個(gè)定義及其含混的指標(biāo),排查 loadavg 高就是一個(gè)很復(fù)雜的過(guò)程。其基本思路就是,根據(jù)引起 Load 變化的根源是 R 狀態(tài)任務(wù)增多,還是 D 狀態(tài)任務(wù)增多,來(lái)進(jìn)入到不同的流程。
這里給出了 Load 增高的排查的一般套路,僅供參考:
在 Linux 系統(tǒng)里,讀取 /proc/stat 文件,即可獲取系統(tǒng)中 R 狀態(tài)的進(jìn)程數(shù);但 D 狀態(tài)的任務(wù)數(shù)恐怕最直接的方式還是使用 ps 命令比較方便。而 /proc/stat 文件里 procs_blocked 則給出的是處于等待磁盤 IO 的進(jìn)程數(shù):
通過(guò)簡(jiǎn)單區(qū)分 R 狀態(tài)任務(wù)增多,還是 D 狀態(tài)任務(wù)增多,我們就可以進(jìn)入到不同的排查流程里。下面,我們就這個(gè)大圖的排查思路,做一個(gè)簡(jiǎn)單的梳理。
R 狀態(tài)任務(wù)增多
即通常所說(shuō)的 CPU 負(fù)載高。此類問(wèn)題的排查定位主要思路是系統(tǒng),容器,進(jìn)程的運(yùn)行時(shí)間分析上,找到在 CPU 上的熱點(diǎn)路徑,或者分析 CPU 的運(yùn)行時(shí)間主要是在哪段代碼上。
CPU user 和 sys 時(shí)間的分布通常能幫助人們快速定位與用戶態(tài)進(jìn)程有關(guān),還是與內(nèi)核有關(guān)。另外,CPU 的 run queue 長(zhǎng)度和調(diào)度等待時(shí)間,非主動(dòng)的上下文切換 (nonvoluntary context switch) 次數(shù)都能幫助大致理解問(wèn)題的場(chǎng)景。
因此,如果要將問(wèn)題的場(chǎng)景關(guān)聯(lián)到相關(guān)的代碼,通常需要使用 perf,systemtap, ftrace 這種動(dòng)態(tài)的跟蹤工具。
關(guān)聯(lián)到代碼路徑后,接下來(lái)的代碼時(shí)間分析過(guò)程中,代碼中的一些無(wú)效的運(yùn)行時(shí)間也是分析中首要關(guān)注的,例如用戶態(tài)和內(nèi)核態(tài)中的自旋鎖 (Spin Lock)。
當(dāng)然,如果 CPU 上運(yùn)行的都是有非常意義,非常有效率的代碼,那唯一要考慮的就是,是不是負(fù)載真得太大了。
D 狀態(tài)任務(wù)增多
根據(jù) Linux 內(nèi)核的設(shè)計(jì), D 狀態(tài)任務(wù)本質(zhì)上是 TASK_UNINTERRUPTIBLE 引發(fā)的主動(dòng)睡眠,因此其可能性非常多。但是由于 Linux 內(nèi)核 CPU 空閑時(shí)間上對(duì) IO 棧引發(fā)的睡眠做了特殊的定義,即 iowait,因此iowait 成為 D 狀態(tài)分類里定位是否 Load 高是由 IO 引發(fā)的一個(gè)重要參考。
當(dāng)然,如前所述, /proc/stat 中的 procs_blocked 的變化趨勢(shì)也可以是一個(gè)非常好的判定因 iowait引發(fā)的 Load 高的一個(gè)參考。
CPU iowait 高
很多人通常都對(duì) CPU iowait 有一個(gè)誤解,以為 iowait 高是因?yàn)檫@時(shí)的 CPU 正在忙于做 IO 操作。其實(shí)恰恰相反, iowait 高的時(shí)候,CPU 正處于空閑狀態(tài),沒(méi)有任何任務(wù)可以運(yùn)行。只是因?yàn)榇藭r(shí)存在已經(jīng)發(fā)出的磁盤 IO,因此這時(shí)的空閑狀態(tài)被標(biāo)識(shí)成了 iowait ,而不是 idle。
但此時(shí),如果用 perf probe 命令,我們可以清楚得看到,在 iowait 狀態(tài)的 CPU,實(shí)際上是運(yùn)行在 pid 為 0 的 idle 線程上:
相關(guān)的 idle 線程的循環(huán)如何分別對(duì) CPU iowait 和 idle 計(jì)數(shù)的代碼,如下所示:
而 Linux IO 棧和文件系統(tǒng)的代碼則會(huì)調(diào)用 io_schedule,等待磁盤 IO 的完成。這時(shí)候,對(duì) CPU 時(shí)間被記為 iowait 起關(guān)鍵計(jì)數(shù)的原子變量 rq->nr_iowait 則會(huì)在睡眠前被增加。注意,io_schedule 在被調(diào)用前,通常 caller 會(huì)先將任務(wù)顯式地設(shè)置成 TASK_UNINTERRUPTIBLE 狀態(tài):
CPU idle 高
如前所述,有相當(dāng)多的內(nèi)核的阻塞,即 TASK_UNINTERRUPTIBLE 的睡眠,實(shí)際上與等待磁盤 IO 無(wú)關(guān),如內(nèi)核中的鎖競(jìng)爭(zhēng),再如內(nèi)存直接頁(yè)回收的睡眠,又如內(nèi)核中一些代碼路徑上的主動(dòng)阻塞,等待資源。
Brendan Gregg 在最近的博客 [Linux Load Averages: Solving the Mystery] (http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html)中,使用 perf 命令產(chǎn)生的 TASK_UNINTERRUPTIBLE 的睡眠的火焰圖,很好的展示了引起 CPU idle 高的多樣性。本文不在贅述。
因此,CPU idle 高的分析,實(shí)質(zhì)上就是分析內(nèi)核的代碼路徑引起阻塞的主因是什么。通常,我們可以使用 perf inject 對(duì) perf record 記錄的上下文切換的事件進(jìn)行處理,關(guān)聯(lián)出進(jìn)程從 CPU 切出 (swtich out) 和再次切入 (switch in) 的內(nèi)核代碼路徑,生成一個(gè)所謂的 Off CPU 火焰圖.
當(dāng)然,類似于鎖競(jìng)爭(zhēng)這樣的比較簡(jiǎn)單的問(wèn)題,Off CPU 火焰圖足以一步定位出問(wèn)題。但是對(duì)于更加復(fù)雜的因 D 狀態(tài)而阻塞的延遲問(wèn)題,可能 Off CPU 火焰圖只能給我們一個(gè)調(diào)查的起點(diǎn)。
例如,當(dāng)我們看到,Off CPU 火焰圖的主要睡眠時(shí)間是因?yàn)?epoll_wait 等待引發(fā)的。那么,我們繼續(xù)要排查的應(yīng)該是網(wǎng)絡(luò)棧的延遲,即本文大圖中的 Net Delay 這部分。
至此,你也許會(huì)發(fā)現(xiàn),CPU iowait 和 idle 高的性能分析的實(shí)質(zhì)就是 延遲分析。這就是大圖按照內(nèi)核中資源管理的大方向,將延遲分析細(xì)化成了六大延遲分析:
- CPU 延遲
 - 內(nèi)存延遲
 - 文件系統(tǒng)延遲
 - IO 棧延遲
 - 網(wǎng)絡(luò)棧延遲
 - 鎖及同步原語(yǔ)競(jìng)爭(zhēng)
 
任何上述代碼路徑引發(fā)的 TASK_UNINTERRUPTIBLE 的睡眠,都是我們要分析的對(duì)象!
以問(wèn)題結(jié)束
限于篇幅,本文很難將其所涉及的細(xì)節(jié)一一展開,因?yàn)樽x到這里,你也許會(huì)發(fā)現(xiàn),原來(lái) Load 高的分析,實(shí)際上就是對(duì)系統(tǒng)的全面負(fù)載分析。怪不得叫 System Load 呢。這也是 Load 分析為什么很難在一篇文章里去全面覆蓋。
本文也開啟了淺談 Linux 性能分析系列的第一章。后續(xù)我們會(huì)推出系列文章,就前文所述的六大延遲分析,一一展開介紹,敬請(qǐng)期待……
關(guān)于作者
楊勇 (Oliver Yang),Linux 內(nèi)核工程師,來(lái)自阿里云系統(tǒng)組。曾就職于 EMC,Sun 中國(guó)工程研究院,在存儲(chǔ)系統(tǒng)和 Solaris 內(nèi)核開發(fā)領(lǐng)域工作。
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的浅谈 Linux 高负载的系统化分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
                            
                        - 上一篇: Oracle数据到MaxCompute乱
 - 下一篇: Flink on Zeppelin 流计