CPU 利用率高但平均负载低

K E*_*son 38 linux troubleshooting cpu-usage load-average

我们遇到了一个奇怪的行为,我们看到 CPU 利用率很高,但平均负载很低。

我们监控系统的以下图表可以最好地说明这种行为。

CPU使用率和负载

在大约 11:57,CPU 利用率从 25% 上升到 75%。平均负载没有显着变化。

我们运行的服务器有 12 个内核,每个内核有 2 个超线程。操作系统将其视为 24 个 CPU。

CPU 利用率数据是通过/usr/bin/mpstat 60 1每分钟运行一次来收集的。all行和%usr列的数据显示在上面的图表中。我确信这确实显示了每个 CPU 数据的平均值,而不是“堆叠”利用率。虽然我们在图表中看到 75% 的利用率,但我们看到一个进程显示在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
Run Code Online (Sandbox Code Playgroud)

Linux 发行版是 Red Hat Enterprise Linux Server release 6.3 (Santiago)

我们在机器上以相当高的负载运行几个 Java Web 应用程序,每台机器每秒 100 个请求。

如果我正确解释了 CPU 利用率数据,当我们有 75% 的 CPU 利用率时,这意味着我们的 CPU 平均有 75% 的时间在执行进程。然而,如果我们的 CPU 有 75% 的时间都处于忙碌状态,我们难道不应该看到更高的平均负载吗?当我们在运行队列中只有 2-4 个作业时,CPU 怎么会忙到 75%?

我们是否正确解释了我们的数据?什么会导致这种行为?

del*_*ray 72

至少在 Linux 上,平均负载和 CPU 利用率实际上是两个不同的东西。平均负载是衡量一段时间内内核运行队列中等待的任务数量(不仅是 CPU 时间,还有磁盘活动)。CPU 利用率是衡量当前 CPU 繁忙程度的指标。单个 CPU 线程保持 100% 一分钟的最大负载可以“贡献”到 1 分钟平均负载是 1。具有超线程(8 个虚拟内核)的 4 核 CPU 都在 100% 持续 1 分钟将贡献 8 到1 分钟平均负载。

通常,这两个数字具有相互关联的模式,但您不能将它们视为相同。您可以拥有接近 0% CPU 利用率的高负载(例如,当您有大量 IO 数据卡在等待状态时),并且您可以拥有 1 和 100% CPU 的负载,当您运行单线程进程时全倾斜。同样在短时间内,您可以看到 CPU 接近 100%,但负载仍然低于 1,因为平均指标还没有“赶上”。

我见过一台服务器的负载超过 15,000(是的,这确实不是打字错误)并且 CPU 百分比接近 0%。发生这种情况是因为 Samba 共享出现问题,并且大量客户端开始陷入 IO 等待状态。如果您看到没有相应 CPU 活动的常规高负载数,则可能存在某种存储问题。在虚拟机上,这也可能意味着在同一 VM 主机上有其他 VM 激烈竞争存储资源。

高负载也不一定是件坏事,大多数时候它只是意味着系统被充分利用,或者可能超出了它的能力跟上(如果负载数量高于处理器内核数量)。在我曾经是系统管理员的地方,他们有人比 Nagios 更密切地观察主系统上的平均负载。当负载很高时,他们会比你说的 SMTP 更快地 24/7 给我打电话。大多数情况下,实际上并没有什么问题,但他们将装载数量与错误联系起来,并像鹰一样观察它。检查后,我的反应通常是系统只是在做它的工作。当然,这是负载超过 15000 的同一个地方(虽然不是同一台服务器),因此有时确实意味着出现问题。您必须考虑系统的用途。如果它是主力,那么预计负载自然会很高。


Mat*_*Ife 27

负载是一个非常具有欺骗性的数字。把它和一粒盐一起吃。

如果您以非常快的速度连续生成许多任务并且完成得非常快,则运行队列中的进程数量太少而无法为它们注册负载(内核每五秒计算一次负载)。

考虑这个例子,在我有 8 个逻辑核心的主机上,这个 python 脚本将在顶部注册大量 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()
Run Code Online (Sandbox Code Playgroud)

另一种实现方式是避免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");
  }
}
Run Code Online (Sandbox Code Playgroud)

这种行为的原因是算法在创建子进程上花费的时间多于运行实际任务(计数到 10000)。尚未创建的任务不能计入“可运行”状态,但它们在生成时会占用 %sys 的 CPU 时间。

所以,答案可能真的是在你的情况下,无论正在做什么工作都会快速连续地产生大量任务(线程或进程)。


Xav*_*cas 5

如果平均负载没有增加太多,那么这只是意味着您的硬件规格和要处理的任务的性质导致了良好的整体吞吐量,避免了它们在任务队列中堆积一段时间。

如果由于平均任务复杂度太高或任务平均处理时间占用太多 CPU 周期而出现争用现象,那么是的,平均负载会增加。

更新 :

我原来的回答可能不清楚,所以我现在澄清一下:

平均负载计算的确切公式是:loadvg = tasks running + tasks waiting (for cores) + tasks blocked.

您绝对可以拥有良好的吞吐量并接近 24 的平均负载,但不会影响任务处理时间。另一方面,您也可能有 2-4 个周期任务完成得不够快,然后您会看到等待(CPU 周期)的任务数量在增长,最终将达到高平均负载。可能发生的另一件事是让任务运行未完成的同步 I/O 操作,然后阻塞核心,降低吞吐量并使等待任务队列增长(在这种情况下,您可能会看到iowait指标发生变化)


K E*_*son 3

虽然 Matthew Ife 的回答非常有帮助,并引导我们走向正确的方向,但这并不完全是导致我们案例中的行为的原因。在我们的例子中,我们有一个使用线程池的多线程 Java 应用程序,为什么没有完成创建实际任务的工作。

然而,线程所做的实际工作是短暂的,包括 IO 等待或同步等待。正如马修在他的回答中提到的,平均负载是由操作系统采样的,因此可能会错过短暂的任务。

我编写了一个 Java 程序来重现该行为。以下 Java 类在我们的一台服务器上生成 28%(堆叠 650%)的 CPU 利用率。执行此操作时,平均负载约为 1.3。这里的关键是线程内部的sleep(),没有它负载计算是正确的。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MultiThreadLoad {

    private ThreadPoolExecutor e = new ThreadPoolExecutor(200, 200, 0l, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(1000), new ThreadPoolExecutor.CallerRunsPolicy());

    public void load() {
        while (true) {
            e.execute(new Runnable() {

                @Override
                public void run() {
                    sleep100Ms();
                    for (long i = 0; i < 5000000l; i++)
                        ;
                }

                private void sleep100Ms() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    public static void main(String[] args) {
        new MultiThreadLoad().load();
    }

}
Run Code Online (Sandbox Code Playgroud)

总而言之,理论上是我们应用程序中的线程闲置很多,然后执行短暂的工作,这就是负载平均计算无法正确采样任务的原因。