简介:双十一压测过程中,常见的问题之一就是load 飙高,通常这个时候业务上都有受影响,比如服务rt飙高,比如机器无法登录,比如机器上执行命令hang住等等。本文就来说说,什么是load,load是怎么计算的,什么情况下load 会飙高,load飙高是不是必然业务受影响。
双十一压测过程中,常见的问题之一就是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等等)读的都是这个文件。
我们一般认为:
- 如果load接近0,意味着系统处于空闲状态
- 如果 1min 平均值高于 5min 或 15min 平均值,则负载正在增加
- 如果 1min 平均值低于 5min 或 15min 平均值,则负载正在减少
- 如果它们高于系统 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 /* 1/exp(5sec/1min) */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
这三个系数是怎么来的呢?公式如下:
- 1884 = 2048/(power(e,(5/(601)))) / 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,这里涉及到定点计算以及其他一些数学知识,不是我们研究的重点,暂时不展开讨论。
我们看看内核中实际代码:
/*
* a1 = a0 * e a * (1 - e)
*/
static inline unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
unsigned long newload;
// FIXED_1 = 2048
newload = load * exp active * (FIXED_1 - exp);
if (active >= load)
newload = FIXED_1-1;
return newload / FIXED_1;
}
就是一个很直观的实现。上面代码中,第一个参数就是上一时刻的load, 第二个参数就是常量系数,第三个参数是active的进程/线程数量(包括runnable 和 uninterruptible)。
2 计算流程
load的计算分为两个步骤:
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 内核开发和疑难问题排查经验,总结了一些经验,以飨读者。
一文说清linux system load
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至123456@qq.com 举报,一经查实,本站将立刻删除。