我们遇到了一种奇怪的行为,即 CPU 利用率很高,但平均负载却很低。
我们的监控系统中的以下图表最好地说明了这种行为。
大约 11:57 时,CPU 利用率从 25% 上升至 75%。平均负载没有明显变化。
我们运行的服务器有 12 个核心,每个核心有 2 个超线程。操作系统将其视为 24 个 CPU。
CPU 利用率数据是通过每分钟运行来收集的。行和列的/usr/bin/mpstat 60 1
数据如上图所示。我确信这确实显示了每个 CPU 数据的平均值,all
%usr
不是“堆叠”利用率。虽然我们在图表中看到 75% 的利用率,但我们在 中看到一个进程显示使用了大约 2000% 的“堆叠”CPU top
。
平均负载数字是/proc/loadavg
每分钟获取的。
uname -a
给出:
Linux ab04 2.6.32-279.el6.x86_64 #1 SMP Wed Jun 13 18:24:36 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux
Linux 发行版是Red Hat Enterprise Linux Server release 6.3 (Santiago)
我们在机器上运行了几个负载相当重的 Java Web 应用程序,每台机器每秒有 100 个请求。
如果我正确理解了 CPU 利用率数据,那么当 CPU 利用率为 75% 时,这意味着我们的 CPU 平均有 75% 的时间在执行某个进程。但是,如果我们的 CPU 有 75% 的时间处于繁忙状态,难道我们不应该看到更高的平均负载吗?当我们的运行队列中只有 2-4 个作业时,CPU 怎么会处于 75% 的繁忙状态?
我们是否正确解读了数据?什么原因导致了这种现象?
答案1
至少在 Linux 上,平均负载和 CPU 利用率实际上是两个不同的东西。平均负载是衡量一段时间内内核运行队列中有多少任务在等待(不仅仅是 CPU 时间,还有磁盘活动)的指标。CPU 利用率是衡量 CPU 当前繁忙程度的指标。单个 CPU 线程在 1 分钟内处于 100% 状态时,最多可以“贡献”1 分钟平均负载的负载是 1。具有超线程的 4 核 CPU(8 个虚拟核心)在 1 分钟内全部处于 100% 状态时,将为 1 分钟平均负载贡献 8。
这两个数字通常具有相互关联的模式,但您不能将它们视为相同。您可能会遇到高负载但 CPU 利用率接近 0%(例如当您有大量 IO 数据处于等待状态时),并且当您有一个单线程进程全速运行时,您可能会遇到负载为 1 且 CPU 利用率为 100% 的情况。此外,在短时间内,您可以看到 CPU 利用率接近 100%,但负载仍低于 1,因为平均指标尚未“赶上”。
我曾见过一台服务器的负载超过 15,000(是的,这不是笔误),而 CPU 百分比接近 0%。这是因为 Samba 共享出现问题,大量客户端开始陷入 IO 等待状态。如果您看到常规的高负载数字而没有相应的 CPU 活动,则很可能存在某种存储问题。在虚拟机上,这也可能意味着同一台 VM 主机上还有其他 VM 在激烈竞争存储资源。
高负载也不一定是一件坏事,大多数时候它只是意味着系统正在被充分利用,或者可能超出了它的能力(如果负载数高于处理器核心数)。在我曾经担任系统管理员的地方,他们让一个人比 Nagios 更密切地关注他们主系统上的平均负载。当负载很高时,他们会 24/7 全天候打电话给我,速度比你说 SMTP 还快。大多数时候实际上什么都没有错,但他们将负载数字与某些错误联系起来,并像鹰一样监视它。检查后,我的反应通常是系统只是在做它的工作。当然,这是负载超过 15000 的同一个地方(虽然不是同一个服务器),所以有时它确实意味着有些事情出了问题。你必须考虑系统的用途。如果它是一台主力机,那么负载自然会很高。
答案2
负载是一个非常具有欺骗性的数字。请谨慎对待。
如果您快速连续地生成许多任务并且这些任务很快就完成,则运行队列中的进程数量太少,无法为它们注册负载(内核每五秒计算一次负载)。
考虑这个例子,在我的具有 8 个逻辑核心的主机上,这个 python 脚本将在 top 中记录较大的 CPU 使用率(约 85%),但几乎没有任何负载。
import os, sys
while True:
for j in range(8):
parent = os.fork()
if not parent:
n = 0
for i in range(10000):
n += 1
sys.exit(0)
for j in range(8):
os.wait()
另一种实现,这种方法避免wait
了 8 个一组的情况(这会使测试结果产生偏差)。在这里,父进程始终尝试将子进程的数量保持在活动 CPU 的数量,这样它就会比第一种方法繁忙得多,并且有望更准确。
/* Compile with flags -O0 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define ITERATIONS 50000
int maxchild = 0;
volatile int numspawned = 0;
void childhandle(
int signal)
{
int stat;
/* Handle all exited children, until none are left to handle */
while (waitpid(-1, &stat, WNOHANG) > 0) {
numspawned--;
}
}
/* Stupid task for our children to do */
void do_task(
void)
{
int i,j;
for (i=0; i < ITERATIONS; i++)
j++;
exit(0);
}
int main() {
pid_t pid;
struct sigaction act;
sigset_t sigs, old;
maxchild = sysconf(_SC_NPROCESSORS_ONLN);
/* Setup child handler */
memset(&act, 0, sizeof(act));
act.sa_handler = childhandle;
if (sigaction(SIGCHLD, &act, NULL) < 0)
err(EXIT_FAILURE, "sigaction");
/* Defer the sigchild signal */
sigemptyset(&sigs);
sigaddset(&sigs, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &sigs, &old) < 0)
err(EXIT_FAILURE, "sigprocmask");
/* Create processes, where our maxchild value is not met */
while (1) {
while (numspawned < maxchild) {
pid = fork();
if (pid < 0)
err(EXIT_FAILURE, "fork");
else if (pid == 0) /* child process */
do_task();
else /* parent */
numspawned++;
}
/* Atomically unblocks signal, handler then picks it up, reblocks on finish */
if (sigsuspend(&old) < 0 && errno != EINTR)
err(EXIT_FAILURE, "sigsuspend");
}
}
出现这种行为的原因是算法创建子进程所花的时间比运行实际任务(计数到 10000)的时间要多。尚未创建的任务不能计入“可运行”状态,但它们在生成时会占用 %sys 的 CPU 时间。
因此,答案可能确实是这样的,无论正在进行什么工作,都会快速连续地产生大量任务(线程或进程)。
答案3
如果平均负载没有增加太多,那么这只意味着您的硬件规格和要处理的任务的性质导致良好的总体吞吐量,从而避免它们在任务队列中堆积一段时间。
如果存在争用现象,例如由于平均任务复杂度太高或任务平均处理时间需要太多 CPU 周期,那么平均负载就会增加。
更新 :
我的原始回答可能不清楚,所以我现在澄清一下:
平均负载计算的具体公式为:loadvg = tasks running + tasks waiting (for cores) + tasks blocked
。
您肯定可以获得良好的吞吐量,并且接近 24 的平均负载,而不会对任务处理时间造成影响。另一方面,您可能有 2-4 个周期性任务没有足够快地完成,那么您将看到等待(等待 CPU 周期)的任务数量不断增加,最终您将达到较高的平均负载。可能发生的另一件事是,任务正在运行未完成的同步 I/O 操作,然后阻塞核心,降低吞吐量并使等待任务队列不断增长(在这种情况下,您可能会看到指标iowait
发生变化)
答案4
平均负载包括磁盘 IO 上被阻塞的任务,因此,只要有 10 个任务尝试从非常慢的磁盘读取数据,就可以轻松实现零 CPU 利用率和 10 的平均负载。因此,繁忙的服务器开始对磁盘造成冲击是很常见的,所有的寻道都会导致大量阻塞任务,从而推高平均负载,而 CPU 使用率会下降,因为所有任务都被阻塞在磁盘上。