笔者:林工
有任何疑问欢迎关注微信公众号:网易游戏运维平台。(长按识别上图二维码)
微信公众号原文链接:面试被问到 Linux loadavg,看这篇推文就不怕被问倒了
文章目录
Linux loadavg 那些事儿
Linux loadavg 是什么,从哪来
在面试的时候我特别喜欢以这个问题开始,来了解面试者对 loadavg 这个指标的了解程度。大部分人都可以用非常标准的答案来回答我:loadavg 是特定时间间隔内运行队列中平均的进程数,这个数字是由内核统计计算得来。
这时候我又会抛出各种问题:
- 这里的运行队列又是什么呢?
- 运行队列里又有哪些类的进程呢?
- 为什么要包含这些种类的进程?
- 内核又是通过什么算法计算特定时间间隔运行队列的平均进程数的呢?
如果上面的问题都可以答的上来,我会最后问:
- loadavg 这个值多高算高?
对于上面的问题,我们一个个来讲。
运行队列(Run Queue)
在内核中,对于 runqueue 的数据结构定义了许多内容,可以参考这里,runqueue 这样的数据结构每个 CPU 核都会维护一个。
这个 runqueue 不仅仅是用来算 loadavg 的,它在 CPU 调度中也起了很大的作用,这里我们只说 loadavg 相关的。
runqueue 里定义的 nr_running
和 nr_uninterruptible
分别放了这个 CPU 核上处于 R 状态和 D 状态的进程数量。
ps manpage 描述这两种状态的含义是这样的:
- R:正在运行的和准备就绪等待运行的进程(running & runnable)
- D:不可中断的进程(通常为 I/O)(uninterruptible)
其中不可中断状态的进程,可以这么理解:
- 这个进程现在基本上不在 CPU 执行指令,但是它还是占用这个 CPU
- 这个进程在做一些事情(大部分是磁盘 I/O),做的这个事情不能被打断。
- 为啥不能被打断,因为系统觉得它做的这个事情很重要,如果打断了可能结果很严重(比如进程正在读写磁盘数据,把它打断了可能会导致进程的数据和磁盘上的数据不一致的情况)
所以现在回答了前两个问题:
- 运行队列是内核为每个 CPU 核维护的一个数据结构(这个数据结构要干很多事情)
- 运行队列里包含了 R 状态的进程和 D 状态的进程
关于第二个问题的答案,在 loadavg 的源码注释里也能找到答案
* Once every LOAD_FREQ:
*
* nr_active = 0;
* for_each_possible_cpu(cpu)
* nr_active += cpu_of(cpu)->nr_running + cpu_of(cpu)->nr_uninterruptible;
为什么是 R 进程加上 D 进程
在很早的时候(早到我还没出生),loadaverage 是只计算 R 进程的。这种计算有什么问题呢?其实很容易想到:
- 如果有一个 I/O 密集型的系统(很多 D 进程,很少 R 进程),只算 R 进程的话它的 loadaverage 会非常低
- 系统正处于高负载而用户看 loadaverage 又很低,这个指标无法真正描述整个系统的负载情况,只能表示 CPU 的负载情况
所以 Linux 在后来的 patch 中,在计算 loadavg 的时候也加上了 D 进程一起计算。起了个名字叫做 active 进程,很形象。
内核又是怎么统计和计算 loadavg 的
loadavg 在 Linux 里面写死了会计算 3 个数值,分别是 1min, 5min, 15min 的 loadavg 结果。那这个结果是怎么算来的呢?(我们以 1min 的值为例)
先想一个最简单的计算方法:
- 在 1min 里间隔相同时间取 n 次 active 的进程数量
- 把 n 个数字加起来除以 n
好像没毛病,来看看内核里是不是这么干的
代码注释里有这么一段
/*
* 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
*/
这是指数加权平均的算法,简单的说就是给每次取样的值赋权:
- 我们上面简单的算法里,每个值的权值都是一样的,1/n
- 但是内核中的算法给每次取样的值赋的权值是不一样的,最新的值权重最大,最老的值权重最小
关于指数加权平均算法的介绍可以看这篇文章
这种算法的好处,在上面的文章中说的是可以节省资源。但是我觉得不仅仅是节省资源,更重要的是它反映了一个这个值的趋势。
举个简单的例子,如果想了解未来的天气情况,前一天温度的参考价值要大于一周前的温度。在计算平均值的时候把不同时间的值进行赋权,越新的数据权值越大,可以比相同权值计算出来的趋势化更明显。
回到 loadavg 的例子,如果每次采样的 active 进程数越来越大,采用指数加权平均计算后的 loadavg 就会比直接除以 n 的 loadavg 要大,这样可以更好的反映系统正处在一个高负载的状态
loadaverage 指标应该怎么看
通常可能会觉得 loadaverage 除以 CPU 核数大于等于 1 说明系统很忙,有性能问题了。然而我们上面提到,loadavg 的计算不仅包括 R 进程,还包括 D 进程,这类的进程又不能简单的除以 CPU 核数。同时,loadavg 的计算总是一段时间内的(最少的时间间隔是 1min),虽然它用了指数加权平均,但是还是无法精确地展现现在的状态。
所以针对系统负载并不能单一地依靠 loadavg 来判断,要结合其他的系统性能指标一起来看。
同时,并没有一个绝对的 loadavg/cpu核数的比值(比如 0.7)可以指导用户当前是否有性能问题,毕竟有的人在双核的机器上跑服务 loadavg 到 11 还是 ok 的(见 Reference)。重要的还是要看趋势,比如说平时 8 核的机器 loadavg 跑到 10 是正常的,突然 loadavg 变成 15 了这时候可能就会有问题。
Reference
Linux Load Averages: Solving the Mystery
如何理解“平均负载”
通俗理解指数加权平均