也说load


要说运维面试最俗的问题,load的定义排第二的话,没有其他话题敢排第一了吧,load定义这个在我司运维面试中,基本是一个必考题目,可惜有很多面官自己也不是很清楚load计算中的各种细节,其中当然也包括我在内。


今天,我们更近一步,深入来看下load,究竟是怎么算出来,希望能探索到这个问题的根部,当然我们认为大家探讨的共同点是基于load值的来源是怎么一回事,基于哪些值计算出来的,简要的说,load值是基于目前可运行进程数与不可中断睡眠状态的进程数量做统计的,是一个衡量系统负载的值。


那么我们抛一个问题,如果初始时刻load为0,突然可运行状态进程计数变为16,并且长期持续下去,那么在1分钟的时候,load一分钟计数值是多少呢?


我相信可能大部分人都会回答是16吧,起初我也是这么认为的,业界有各种资料解释,load1分钟的值是1分钟内的统计值平均数,5分钟值是5分钟内的统计值平均数……但是,我不知道有人压测过没有,实际上在上面这个场景下,1分钟后的load1分钟值,大概在10左右。


是不是有点惊讶?天呐,为什么是这个样子?下来我们从源头的地方看下,为什么是这么一个情况。


首先放两个不错的还可以参考一下的链接,一个brendan gregg大神的介绍,当然这个侧重点也不在计算上,不过里面有一个图很不错,可能更能体现我上面描述的场景:http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html


另一个是阿里大佬的一篇文章:https://yq.aliyun.com/articles/484253?utm_content=m_42447


关于我在寻找load含义的过程中,也仅有这两篇文章能给一些不错的启发,说实在的,其余市面搜索到的文章,99%都是错的,当然错误的根源实际上来自某大佬2011年的一篇翻译自国外的文章,这也让我认识到,试图阐释明白这个问题,其实是有点难度的,因此我尽量去解释明白这个问题,希望可以抛砖引玉吧


计算load的方法,可以参考这个链接的这段代码:

/*
 * a1 = a0 * e + a * (1 - e)
 *
 * a2 = a1 * e + a * (1 - e)
 *    = (a0 * e + a * (1 - e)) * e + a * (1 - e)
 *    = a0 * e^2 + a * (1 - e) * (1 + e)
 *
 * a3 = a2 * e + a * (1 - e)
 *    = (a0 * e^2 + a * (1 - e) * (1 + e)) * e + a * (1 - e)
 *    = a0 * e^3 + a * (1 - e) * (1 + e + e^2)
 *
 *  ...
 *
 * an = a0 * e^n + a * (1 - e) * (1 + e + ... + e^n-1) [1]
 *    = a0 * e^n + a * (1 - e) * (1 - e^n)/(1 - e)
 *    = a0 * e^n + a * (1 - e^n)
 *
 * [1] application of the geometric series:
 *
 *              n         1 - x^(n+1)
 *     S_n := \Sum x^i = -------------
 *             i=0          1 - x
 */

 


再参照下wiki:移动平均


关键的地方:a1 = a0 * e + a * (1 – e),可以看出,每一个当前的值a1,是前一个值a0乘以一个常量e加当前的可运行进程数乘以(1-e),e是一个介于0-1之间的数,e的取值,实际上是这个值:e-5/60R

这里很明显了,如果e越大,那么统计结果越接近上一个时刻的值,e越小,那么统计结果越接近当前的可运行进程数。事实上,load1,load5,load15,三种值的区别只是在于e的不同,所以导致三条曲线的增长趋势不同。

这里我们描述下计算load1,load5,load15的计算方式:

首先是三个常量的定义:

EXP_1 = e^(-5/(60*1)) //约等于0.9200
EXP_5 = e^(-5/(60*5)) //约等于0.9835
EXP_15 = e^(-5/(60*15)) //约等于0.9945

接下来是三个公式:

load1_1 = load1_0 * EXP_1 + c * (1 - EXP_1)
load5_1 = load5_0 * EXP_5 + c * (1 - EXP_5)
load15_1 = load15_0 * EXP_15 + c * (1 - EXP_15)

其中,load1_1表示load1的当前时刻值,load1_0表示load1的上一个时刻值(5s前),c表示当前可运行进程数。

三个公式可以泛化表示为:

loadx_1 = loadx_0 * EXP_x + c * (1 - EXP_x)

接下来来一个关键的推理过程,假设可运行进程数c不变,经过n个周期之后,新的load跟原来的load是什么关系呢?

f(z) = z * EXP_x + c * (1 - EXP_x) //第一次计算load,初始值为z
f(f(z)) = (z * EXP_x + c * (1 - EXP_x)) * EXP_x + c * (1 - EXP_x)  //第二次计算load
        = z * EXP_x ^ 2 + (1 - EXP_x ^ 2) * c
f(f(f(z))) = (z * EXP_x ^ 2 + (1 - EXP_x ^ 2) * n) * EXP_x + c * (1 - EXP_x)  //第三次计算load
           = z * EXP_x ^ 3 + (1 - EXP_x ^ 3) * c
......
f^n(z) = (z * EXP_x ^ n + (1 - EXP_x ^ n) * c) * EXP_x + c * (1 - EXP_x)  //迭代函数表示
       = z * EXP_x ^ n + (1 - EXP_x ^ n) * c
//反推求c的公式:
c = (f^n(z) - z * EXP_x ^ n) / (1 - EXP_x ^ n)

接下来我们验证下,现在我们有如下一个load序列(我们的监控粒度是10s一次,linux load取值是5s一次):

load1:    26.55    53.22    75.79
load5:    5.67     12.07    18.26
load15:   1.88     4.01     6.11

通过反推负载的公式计算出来,可运行进程数序列为:

200.27, 200.23
200.88, 200.88
194.64, 194.06

现实中我们确实是用200个sysbench线程去压测的,可以看出反推的公式也是非常靠谱的。

掌握了这个计算方式,就可以计算load跟可运行进程数了,但是,我们做了这么,其实不如直接监控可运行进程数更好一点呢,这个值也在/proc/loadavg里面哦,第四列的前半部分。

此条目发表在好玩的linux分类目录。将固定链接加入收藏夹。