找到Linux虛機Load高的"元兇"

 

問題描述

有客戶反饋他們的一臺ECS週期性地load升高,他們的業務流量並沒有上升,需要我們排查是什麼原因造成的,是否因爲底層異常?

要弄清Linux虛機load高,我們要搞清楚Linux top命令中Load的含義。

Load average的值從何而來

在使用top命令檢查系統負載的時候,可以看到Load averages字段,但是這個字段並不是表示CPU的繁忙程度,而是度量系統整體負載。

Load averages採樣是從/proc/loadavg中獲取的:

0.00 0.01 0.05 1/161 29703
每個值的含義依次爲:
lavg_1 (0.00) 1-分鐘平均負載
lavg_5 (0.01) 5-分鐘平均負載
lavg_15(0.05) 15-分鐘平均負載
nr_running (1) 在採樣時刻,運行隊列的任務的數目,與/proc/stat的procs_running表示相同意思,這個數值是當前可運行的內核調度對象(進程,線程)。
nr_threads (161) 在採樣時刻,系統中活躍的任務的個數(不包括運行已經結束的任務),即這個數值表示當前存在系統中的內核可調度對象的數量。
last_pid(29703) 系統最近創建的進程的PID,包括輕量級進程,即線程。
假設當前有兩個CPU,則每個CPU的當前任務數爲0.00/2=0.00

如果你看到load average數值是10,則表明平均有10個進程在運行或等待狀態。有可能系統有很高的負載但是CPU使用率卻很低,或者負載很低而CPU利用率很高,因爲這兩者沒有直接關係。
Linux 源碼中關於這一塊的說明:
static int loadavg_proc_show(struct seq_file *m, void *v)
{
        unsigned long avnrun[3];

        get_avenrun(avnrun, FIXED_1/200, 0);

        seq_printf(m, "%lu.%02lu %lu.%02lu %lu.%02lu %ld/%d %d\n",
                LOAD_INT(avnrun[0]), LOAD_FRAC(avnrun[0]),
                LOAD_INT(avnrun[1]), LOAD_FRAC(avnrun[1]),
                LOAD_INT(avnrun[2]), LOAD_FRAC(avnrun[2]),
                nr_running(), nr_threads,
                task_active_pid_ns(current)->last_pid);
        return 0;
}

Load的計算函數:

static unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
        load *= exp;
        load += active * (FIXED_1 - exp);
        return load >> FSHIFT;
}

/*
 * calc_load - update the avenrun load estimates 10 ticks after the
 * CPUs have updated calc_load_tasks.
 */
void calc_global_load(void)
{
        unsigned long upd = calc_load_update + 10;
        long active;

        if (time_before(jiffies, upd))
                return;

        active = atomic_long_read(&calc_load_tasks);
        active = active > 0 ? active * FIXED_1 : 0;

        avenrun[0] = calc_load(avenrun[0], EXP_1, active);
        avenrun[1] = calc_load(avenrun[1], EXP_5, active);
        avenrun[2] = calc_load(avenrun[2], EXP_15, active);

        calc_load_update += LOAD_FREQ;
}
/*
 * These are the constant used to fake the fixed-point load-average
 * counting. Some notes:
 *  - 11 bit fractions expand to 22 bits by the multiplies: this gives
 *    a load-average precision of 10 bits integer + 11 bits fractional
 *  - if you want to count load-averages more often, you need more
 *    precision, or rounding will get you. With 2-second counting freq,
 *    the EXP_n values would be 1981, 2034 and 2043 if still using only
 *    11 bit fractions.
 */
extern unsigned long avenrun[];         /* Load averages */
extern void get_avenrun(unsigned long *loads, unsigned long offset, int shift);

#define FSHIFT          11              /* nr of bits of precision */
#define FIXED_1         (1<<FSHIFT)     /* 1.0 as fixed-point */
#define LOAD_FREQ       (5*HZ+1)        /* 5 sec intervals */
#define EXP_1           1884            /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5           2014            /* 1/exp(5sec/5min) */
#define EXP_15          2037            /* 1/exp(5sec/15min) */

#define CALC_LOAD(load,exp,n) \
        load *= exp; \
        load += n*(FIXED_1-exp); \
        load >>= FSHIFT;

從這個函數中可以看到,內核計算load採用的是一種平滑移動的算法,Linux的系統負載指運行隊列的平均長度,需要注意的是:可運行的進程是指處於運行隊列的進程,不是指正在運行的進程。即進程的狀態是TASK_RUNNING或者TASK_UNINTERRUPTIBLE。

Linux內核定義一個長度爲3的雙字數組avenrun,雙字的低11位用於存放負載的小數部分,高21位用於存放整數部分。當進程所耗的 CPU時間片數超過CPU在5秒內能夠提供的時間片數時,內核計算上述的三個負載,負載初始化爲0。

假設最近1、5、15分鐘內的平均負載分別爲 load1、load5和load15,那麼下一個計算時刻到來時,內核通過下面的算式計算負載:
load1 -= load1 - exp(-5 / 60) -+ n (1 - exp(-5 / 60 ))
load5 -= load5 - exp(-5 / 300) + n (1 - exp(-5 / 300))
load15 = load15 exp(-5 / 900) + n (1 - exp(-5 / 900))
其中,exp(x)爲e的x次冪,n爲當前運行隊列的長度。

如何找出系統中load高時處於運行隊列的進程

通過前面的講解,我們已經明白有可能系統有很高的負載但是CPU使用率卻很低,或者負載很低而CPU利用率很高,這兩者沒有直接關係,如何用腳本統計出來處於運行隊列的進程呢?

每隔1s統計一次:

#!/bin/bash
LANG=C
PATH=/sbin:/usr/sbin:/bin:/usr/bin
interval=1
length=86400
for i in $(seq 1 $(expr ${length} / ${interval}));do
date
LANG=C ps -eTo stat,pid,tid,ppid,comm --no-header | sed -e 's/^ \*//' | perl -nE 'chomp;say if (m!^\S*[RD]+\S*!)'
date
cat /proc/loadavg
echo -e "\n"
sleep ${interval}
done

從統計出來的結果可以看到:

at Jan 20 15:54:12 CST 2018
D      958   958   957 nginx
D      959   959   957 nginx
D      960   960   957 nginx
D      961   961   957 nginx
R      962   962   957 nginx
D      963   963   957 nginx
D      964   964   957 nginx
D      965   965   957 nginx
D      966   966   957 nginx
D      967   967   957 nginx
D      968   968   957 nginx
D      969   969   957 nginx
D      970   970   957 nginx
D      971   971   957 nginx
D      972   972   957 nginx
D      973   973   957 nginx
D      974   974   957 nginx
R      975   975   957 nginx
D      976   976   957 nginx
D      977   977   957 nginx
D      978   978   957 nginx
D      979   979   957 nginx
R      980   980   957 nginx
D      983   983   957 nginx
D      984   984   957 nginx
D      985   985   957 nginx
D      986   986   957 nginx
D      987   987   957 nginx
D      988   988   957 nginx
D      989   989   957 nginx
R    11908 11908 18870 ps
Sat Jan 20 15:54:12 CST 2018
25.76 20.60 19.00 12/404 11912
注:R代表運行中的隊列,D是不可中斷的睡眠進程

在load比較高的時候,有大量的nginx處於R或者D狀態,他們纔是造成load上升的元兇,和我們底層的負載確實是沒有關係的。

最後也給大家share一下查CPU使用率比較高的線程小腳本:

#!/bin/bash
LANG=C
PATH=/sbin:/usr/sbin:/bin:/usr/bin
interval=1
length=86400
for i in $(seq 1 $(expr ${length} / ${interval}));do
date
LANG=C ps -eT -o%cpu,pid,tid,ppid,comm | grep -v CPU | sort -n -r | head -20
date
LANG=C cat /proc/loadavg
{ LANG=C ps -eT -o%cpu,pid,tid,ppid,comm | sed -e 's/^ *//' | tr -s ' ' | grep -v CPU | sort -n -r | cut -d ' ' -f 1 | xargs -I{} echo -n "{} + " && echo ' 0'; } | bc -l
sleep ${interval}
done
fuser -k $0

轉載自:https://yq.aliyun.com/articles/484253?utm_content=m_42447

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章