雙十一壓測過程中,常見的問題之一就是load 飆高,通常這個時候業務上都有受影響,比如服務rt飆高,比如機器無法登入,比如機器上執行命令hang住等等。本文就來說說,什麼是load,load是怎麼計算的,什麼情況下load 會飆高,load飆高是不是必然業務受影響。
一 什麼是load
我們平時所講的load,其全稱是Linux system load averages ,即linux系統負載平均值。注意兩個關鍵詞:一個是“負載”,它衡量的是task(linux 核心中用於描述一個程序或者執行緒)對系統的需求(CPU、記憶體、IO等等),第二個關鍵詞是“平均”,它計算的是一段時間內的平均值,分別為 1、5 和 15 分鐘值。system load average由核心負載計算並記錄在/proc/loadavg 檔案中, 使用者態的工具(比如uptime,top等等)讀的都是這個檔案。
-
-
2、如果 1min 平均值高於 5min 或 15min 平均值,則負載正在增加;
-
3、如果 1min 平均值低於 5min 或 15min 平均值,則負載正在減少;
-
4、如果它們高於系統 CPU 的數量,那麼系統很可能遇到了效能問題(視情況而定)。
二 如何計算load
1 核心演算法
坦白了不裝了,核心演算法其實就是指數加權移動平均法(Exponential Weighted Moving Average,EMWA),簡單表示就是:
a1 = a0 * factor + a * (1 – factor),其中a0是上一時刻的值,a1是當前時刻的值,factor是一個係數,取值範圍是[0,1],a是當前時刻的某個指標取樣值。
1、指數移動加權平均法,是指各數值的加權係數隨時間呈指數式遞減,越靠近當前時刻的數值加權係數就越大,更能反映近期變化的趨勢;
2、計算時不需要儲存過去所有的數值,這對核心非常重要。
我們來看看,核心是怎麼計算load average的,以下簡稱load。
上面的指數移動平均公式,a1 = a0 * e + a * (1 – e),具體到linux load的計算,a0是上一時刻的load,a1是當前時刻的load,e是一個常量係數,a 是當前時刻的active的程序/執行緒數量。
如上一節所述,linux 核心計算了三個load 值,分別是1分鐘/5分鐘/15分鐘 load 。計算這三個load 值時,使用了三個不同的常量係數e,定義如下:
#define EXP_1 1884
#define EXP_5 2014
#define EXP_15 2037
-
1884 = 2048/(power(e,(5/(60*1)))) /* e = 2.71828 */
-
2014 = 2048/(power(e,(5/(60*5))))
-
2037 = 2048/(power(e,(5/(60*15))))
其中e=2.71828,其實就是自然常數e,也叫尤拉數(Euler number)。
那為什麼是這麼個公式呢?其中,5是指每五秒取樣一次,60是指每分鐘60秒,1、5、15則分別是1分鐘、5分鐘和15分鐘。至於為什麼是2048和自然常數e,這裡涉及到定點計算以及其他一些數學知識,不是我們研究的重點,暫時不展開討論。
staticinlineunsignedlong
calc_load(unsignedlong load, unsignedlongexp, unsignedlong active)
{
unsignedlong newload;
newload = load * exp + active * (FIXED_1 - exp);
if (active >= load)
newload += FIXED_1-1;
return newload / FIXED_1;
}
就是一個很直觀的實現。上面程式碼中,第一個引數就是上一時刻的load, 第二個引數就是常量係數,第三個引數是active的程序/執行緒數量(包括runnable 和 uninterruptible)。
2 計算流程
1、週期性地更新每個CPU上的rq裡的active tasks,包括runnable狀態和uninterruptible狀態的task,累加到一個全域性變數calc_load_tasks。
2、週期性地計算 load,load的計算主要就是基於上述calc_load_tasks 變數。
第一個步驟,每個cpu都必須更新calc_load_tasks,但是第二個步驟只由一個cpu來完成,這個cpu叫tick_do_timer_cpu,由它執行do_timer() -> calc_global_load()計算系統負載。
整體流程如下圖所示,在每個tick到來時(時鐘中斷),執行以下邏輯:
上圖中,棕色的calc_global_load_tick函式就是完成第一個步驟的,綠色的calc_global_load 是完成第二個步驟,藍色的calc_load 就是上一節中描述的核心演算法。
這裡需要說明的是,calc_global_load 把計算出來的load 值放在一個全域性的變數avenrun中,它的定義是unsigned long avenrun[3],size 是3,用於存放1/5/15分鐘的load。 當檢視/proc/loadavg的時候,就是從這個avenrun陣列中獲取資料。
三 load高常見原因
從上述load的計算原理可以看出,導致load 飆高的原因,說簡單也簡單,無非就是runnable 或者 uninterruptible 的task 增多了。但是說複雜也複雜,因為導致task進入uninterruptible狀態的路徑非常多(粗略統計,可能有400-500條路徑)。個人覺得,有些地方有點濫用這個狀態了。
本人基於多年的linux 核心開發和疑難問題排查經驗,總結了一些經驗,以饗讀者。
1 週期性飆高
曾經有些業務方遇到過load週期性飆高的現象,如果不是因為業務上確實有週期性的峰值,那麼大機率是踩中了核心計算load時的bug。這個bug和核心的load取樣頻率( LOAD_FREQ)有關,具體細節不展開討論。這個bug在ali2016,ali3000, ali4000中已經修復。
排除這個原因的話,可以接著檢視是否磁碟IO的原因。
2 IO原因
磁碟效能瓶頸
iostat -dx 1 可以檢視所有磁碟的IO 負載情況,當IOPS 或者 BW 高時,磁碟成為效能瓶頸,大量執行緒因為等待IO而處於uninterruptible 狀態,導致load飆高。此時如果用vmstat 檢視,可能會觀察到 b 這一列的數值飆高, cpu iowait 飆高,/proc/stat檔案中的procs_blocked 數值飆高。
雲盤異常
雲盤是虛擬盤,IO路徑長而複雜,比較容易出問題。常見的異常是IO UTIL 100%,avgqu-sz 始終不為0,至少有1 。大家不要誤解,io util 100%並不意味著磁碟很忙,而只意味著這個裝置的請求佇列裡在每次取樣時都發現有未完成的IO請求,所以當某種原因導致IO丟失的話,雲盤就會出現UTIL 100%, 而ECS核心裡的jbd2 執行緒,業務執行緒也會被D住,導致load 飆高。
JBD2 bug
JBD2是ext4 檔案系統的日誌系統,一旦jbd2 核心執行緒由於bug hang住,所有的磁碟IO請求都會被阻塞,大量執行緒進入uninterruptible狀態,導致load 飆高。
3 記憶體原因
記憶體回收
task 在申請記憶體的時候,可能會觸發記憶體回收,如果觸發的是直接記憶體回收,那對效能的傷害很大。當前task 會被阻塞直到記憶體回收完成,新的請求可能會導致task數量增加(比如HSF執行緒池擴容),load 就會飆高。 可以透過tsar –cpu –mem –load -i1 -l 檢視,一般會觀察到sys cpu 飆高,cache 突降等現象。
記憶體頻寬競爭
大家可能只聽說過IO頻寬,網路頻寬,很少注意記憶體頻寬。其實記憶體除了在容量維度有瓶頸,在頻寬層面也有瓶頸,只是這個指標普通的工具觀察不了。我們開發的aprof 工具可以觀察記憶體頻寬競爭,在雙十一保障期間在混部環境大顯神威。
4 鎖
通常是核心某些路徑上的spin_lock會成為瓶頸,尤其是網路的收發包路徑上。可以用perf top -g 檢視到spin_lock的熱點, 然後根據函式地址找到核心的原始碼。 伴隨的現象可能有sys 飆高,softirq 飆高。
另外,採用mutex_lock進行併發控制的路徑上,一旦有task 拿著lock 不釋放,其他的task 就會以TASK_UNINTERRUPTIBLE的狀態等待,也會引起load飆高。但是如果這把鎖不在關鍵路徑上,那麼對業務可能就沒啥影響。
5 user CPU
有些情況下load飆高是業務的正常表現,此時一般表現為user cpu 飆高,vmstat 看到 r 這一列升高,tsar –load -i1 -l 看到runq 升高,檢視proc/pid/schedstats 可能會看到第二個數字也就是sched delay 會增加很快。
四 根因分析大招
1 RUNNABLE 型load飆高分析
如上所述,這種情況,通常是由於業務量的增加導致的,屬於正常現象,但也有是業務程式碼bug導致的,比如長迴圈甚至死迴圈。但無論哪一種,一般都可以透過熱點分析或者叫on cpu分析找到原因。on cpu分析的工具比較多,比如perf,比如阿里自研的ali-diagnose perf等等。
2 UNINTERRUPTIBLE型load飆高分析
所謂UNINTERRUPTIBLE,就是意味著在等,所以我們只要找到等在哪裡,就基本找到原因了。
查詢UNINTERRUPTIBLE狀態程序
UNINTERRUPTIBLE,通常也稱為D狀態,下文就用D狀態來描述。有一些簡單的工具可以統計當前D狀態程序的數量, 稍微複雜一點的工具可以把D狀態程序的呼叫鏈也就是stack輸出。這類工具一般都是從核心提供的proc 檔案系統取數。
檢視/proc/${pid}/stat 以及/proc/${pid}/task/${pid}/stat 檔案,可以判斷哪些task 處於D狀態,如下所示:
第三個欄位就是task的狀態。然後再檢視/proc/${pid}/stack 檔案就可以知道task 等在哪裡。如:
但有時候,D狀態的task 不固定,這會導致抓不到D狀態或者抓到stack的不準確。這時候,就得上另一個終極大招,延遲分析。
延遲分析
延遲分析需要深入核心內部,在核心路徑上埋點取數。所以這類工具的本質是核心probe,包括systemtap,kprobe,ebpf等等。但是probe 技術必須結合知識和經驗才能打造成一個實用的工具。阿里自研的ali-diagnose可以進行各種delay分析,irq_delay, sys_delay, sched_delay, io_delay, load-monitor。
五 總結
linux 核心是一個複雜的併發系統,各模組關係錯綜複雜。但是就load 而言,只要從runnable task和 uninterruptible task兩個維度進行分析,總能找到根源。