一次有趣的High load average排查暨深刻理解Linux系统负载

当Linux服务运行慢的时候,我们一般都是用top命令看看当前CPU使用率以及哪些进程占用了资源等。资深一些管理员,会通过查看load average来查看系统负载以评估系统是越来越慢还是会越来越快。正常情况下,系统没有活跃进程时,系统的负载是0, 如下所示(用uptime):

1
2
# uptime
03:07am up 0:38, 2 users, load average: 0.00, 0.00, 0.02

可是有一次有台机会变慢,load average很高,但是CPU 使用率接近于0,如下所示:

1
2
3
4
5
6
top - 05:15:28 up  2:25,  2 users,  load average: 18.44, 18.47, 18.32
Tasks: 144 total, 1 running, 143 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.8 sy, 0.0 ni, 99.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

如果是使用vmstats/iostat,都没办法发现是什么导致High load average和机器变慢。在大量Google之后,最终怀疑到是因为有一个NFS mount的服务器可能down了,大量的df进程导致系统High load average,下面我们细细分析个中原因。

什么是Load Average

Load表示是Linux系统中对当前CPU工作量的度量,简单来说就是CPU需要运行进程数量的队列长度(==不是CPU使用比率,所以值是会大于1==)。而Load average则是分别统计系统1分钟、5分钟、15分钟平均Load。 如上面top所示,1分钟平均负载是18.44,5分钟平均负载是18.49,15分钟平均负载是17.46。
当平均负载远高于系统CPU数量,则意味系统负载比较重。如果1分钟负载比较低而15分钟负载比较高,则意味着系统负载将降低。反之,则意味着系统负载将越来越高。这时候我们需要关注是否某些进程消耗大量I/O,CPU或者Memory等。
Load Average的评估经常需要和当前系统CPU数量关联起来分析。

- 单核CPU

假设我们的系统是单核的CPU,把它比喻成是一条单向马路,把CPU任务比作汽车。当车占满整个马路的时候 load=1;当车不多的时候,load <1;当马路都站满了,而且马路外还堆满了汽车的时候,load>1,如下所示:
在这里插入图片描述

- 多核CPU

现代机器基本都是多核CPU,所以即便有时候Load average比较高,系统任然可以运行的比较顺畅。假设我们服务器CPU是2核,那么将意味我们拥有2条马路,我们的Load = 2时,所有马路都跑满车辆,如下所示:
在这里插入图片描述

Load Average是如何计算出来的

无论是uptime还是top命令输入的load average其实是读取了文件/proc/loadavg:

1
2
# cat /proc/loadavg
18.60 18.51 17.67 2/178 11202

前面三个数字是1、5、15分钟内的平均进程数,第四数字是当前运行的进程数量(分子)和总进程数(分母),第五个数字是最后使用的进程ID。
/proc/loadavg实际上是内核schedule进程更新,通过proc fs 曝露给user的。我们通过查看一下Linux 内核源代码就可以窥探一二Load average是如何计算出来的。
include/linux/sched/loadavg.h定义了一定时间Load Average的计算算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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) */

/*
* a1 = a0 * e + a * (1 - e)
*/
static inline unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
unsigned long newload;

newload = load * exp + active * (FIXED_1 - exp);
if (active >= load)
newload += FIXED_1-1;

return newload / FIXED_1;
}

kernel/sched/loadavg.c计算active task 数量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
long calc_load_fold_active(struct rq *this_rq, long adjust)
{
long nr_active, delta = 0;

nr_active = this_rq->nr_running - adjust;
nr_active += (long)this_rq->nr_uninterruptible;

if (nr_active != this_rq->calc_load_active) {
delta = nr_active - this_rq->calc_load_active;
this_rq->calc_load_active = nr_active;
}

return delta;
}

更多细节可以通过查看源代码来理解。

为什么NFS hang会导致High Load Average

现在我们已经知道Load average是咋么计算出来以及它的意义,我们会过来分析为什么NFS hang会导致High Load Average。其实这是Linux对Load计算一个缺陷。Linux上的load average除了包括正在使用CPU的进程数量和正在等待CPU的进程数量之外,还包括uninterruptible sleep的进程数量(==如函数calc_load_fold_active()所示==)。通常等待IO设备、等待网络的时候,进程会处于uninterruptible sleep状态。Linux设计者的逻辑是,uninterruptible sleep应该都是很短暂的,很快就会恢复运行,所以被等同于runnable。但是对于NFS 来说,假设服务器一直没有恢复,那么任何对于该mount目录操作都将hang住,该进程会一直处于uninterruptible状态。在本例中,由于有个cron job定期统计NFS磁盘使用量,导致有大量df进程hang住。如下所示(D表示uninterruptible):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ps -auxwww | grep D
zgqadm 7675 0.0 0.0 5816 1320 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7676 0.0 0.0 5816 1336 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7677 0.0 0.0 5816 1356 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7678 0.0 0.0 5816 1344 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7679 0.0 0.0 5816 1308 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7680 0.0 0.0 5816 1352 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7681 0.0 0.0 5816 1308 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7682 0.0 0.0 5816 1356 tty1 D 04:02 0:00 df -Pk /nfsshare
zgqadm 7761 0.0 0.0 5816 1324 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7762 0.0 0.0 5816 1344 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7763 0.0 0.0 5816 1376 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7764 0.0 0.0 5816 1340 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7765 0.0 0.0 5816 1260 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7766 0.0 0.0 5816 1360 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7767 0.0 0.0 5816 1316 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7768 0.0 0.0 5816 1260 tty1 D 04:03 0:00 df -Pk /nfsshare
zgqadm 7769 0.0 0.0 5816 1376 tty1 D 04:03 0:00 df -Pk /nfsshare

参考链接

[1] Understanding Linux CPU Load - when should you be worried
[2] Linux Load Averages: Solving the Mystery
[3] 理解LINUX LOAD AVERAGE的误区

Comments