找到Linux虚机(ECS)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 { 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
共 0 条评论