我们遇到了一个奇怪的行为,我们看到高CPU利用率,但平均负载很低。
我们的监测系统中的下列图表可以很好地说明这一现象。
在大约11:57,CPU利用率从25%上升到75%。 平均负载没有显着变化。
我们运行12个核心的服务器,每个都有2个超线程。 操作系统认为这是24个CPU。
通过每分钟运行/usr/bin/mpstat 60 1
来收集CPU利用率数据。 all
行和%usr
列的数据如上图所示。 我确信这确实显示了每个CPU数据的平均值, 而不是 “堆积”利用率。 虽然我们在图表中看到75%的利用率,但是我们看到一个过程显示使用大约2000%的“堆叠式”CPU。
平均负载数字取自/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 dist是Red Hat Enterprise Linux Server release 6.3 (Santiago)
我们在相当重的负载下运行一些Java Web应用程序,认为每台机器100个请求/秒。
如果我正确解释CPU利用率数据,当我们有75%的CPU使用率时,这意味着我们的CPU平均在75%的时间内执行一个进程。 但是,如果我们的CPU占用75%的时间,我们不应该看到更高的平均负载吗? 如何在运行队列中只有2-4个作业的情况下CPU占用率达到75%?
我们是否正确解释我们的数据? 什么会导致这种行为?
至less在Linux上,平均负载和CPU利用率实际上是两回事。 负载平均值是一段时间内内核运行队列中等待多less任务(不仅仅是CPU时间,还有磁盘活动)的度量。 CPU使用率是衡量CPU现在的繁忙程度的一个指标。 单线程100%挂一分钟的负载可以“贡献”到1分钟的平均负载为1.一个4核心的超线程(8个虚拟内核),全部在100%,1分钟将贡献8 1分钟的平均负载。
这两个数字通常有相互关联的模式,但是你不能把它们想成是一样的。 您可以在CPU使用率接近0%的情况下(例如,当IO数据处于等待状态时)获得高负载,并且只有一个线程进程运行时,您可以拥有1%和100%的CPU负载完全倾斜。 同样在短时间内,您可以看到CPU接近100%,但负载仍然低于1,因为平均指标还没有“赶上”。
我见过一个服务器负载超过15,000(是的,这不是一个错字)和接近0%的CPU%。 发生这种情况是因为Samba共享有问题,很多客户开始陷入IO等待状态。 如果你看到一个没有相应的CPU活动的正常的高负载数,你可能会遇到某种存储问题。 在虚拟机上,这也意味着有其他虚拟机在同一个VM主机上竞争存储资源。
负载是一个非常具有欺骗性的数字。 拿一粒盐。
如果以很快的速度连续产生许多任务,则运行队列中的进程数量太小,无法为其注册负载(内核每五秒计数一次)。
考虑一下这个例子,在我的主机上有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()
另一个实现,这个避免了wait
8个小组(这会歪曲testing)。 在这里父母总是试图保持活跃的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"); } }
这种行为的原因是该algorithm花费更多的时间创buildsubprocess比运行实际任务(计数到10000)。 尚未创build的任务不能计入“可运行”状态,但在生成CPU时间时会占用%sys。
所以,对于你来说,无论做什么工作,都可以快速连续地产生大量的任务(线程或进程)。
如果平均负载没有增加,那么这意味着您的硬件规格和要处理的任务的性质会产生一个很好的整体吞吐量,避免它们在任务队列中堆积一段时间。
如果存在竞争现象,例如平均任务复杂度太高或者任务平均处理时间需要太多的CPU周期,那么是的,则平均负载会增加。
更新:
我原来的答案可能并不清楚,所以现在我正在澄清:
负载平均计算的确切公式为: loadvg = tasks running + tasks waiting (for cores) + tasks blocked
。
你可以肯定有一个很好的吞吐量,并接近24的平均负载,但没有惩罚的任务处理时间。 另一方面,你也可以有2-4个周期性的任务没有足够快的完成,那么你会看到等待(CPU周期)的任务数量增长,你最终将达到高负载平均。 另一件可能发生的事情是让任务运行未完成的同步I / O操作,然后阻塞一个内核,降低吞吐量并使等待的任务队列增长(在这种情况下,您可能会看到iowait
度量标准发生变化)
平均负载包括在磁盘IO上被阻塞的任务,所以你可以很容易地获得零CPU的利用率,并且只要10个任务都试图从一个非常慢的磁盘上读取,平均负载就是10。 因此,一个繁忙的服务器开始抖动磁盘是很常见的,而且所有的查找都会导致很多被阻塞的任务,从而提高了平均负载,同时cpu使用率下降,因为所有的任务都被阻塞在磁盘上。
平均负载是CPU队列中的平均进程数。 对每个系统来说都是具体的,你不能说一个LA在所有系统上一般都很高,而另一个是低的。 所以你有12个内核,而且LA要显着增加,进程的数量必须非常高。
另一个问题是“CPU使用率”图的含义。 如果它来自SNMP,就像它应该是,而你的SNMP实现是net-snmp
,那么就从你的每一个CPU中加载CPU负载。 所以对于net-snmp
,CPU负载总量是1200%。
如果我的假设是正确的,那么CPU使用率没有显着增加。 因此,洛杉矶没有显着增加。
这里的情况并不特别出乎意料,虽然有点不寻常。 Xavier接触的是什么,但并没有多less发展,尽pipeLinux(默认情况下)和Unix的多数风格实现了先发制人的多任务处理,但在一台健康的机器上,任务很less被抢占。 每个任务都占用一个CPU时间片,如果超过这个时间,则只会被抢占,并且还有其他任务正在等待运行(请注意,负载会报告CPU中的平均进程数并等待运行) 。 大多数情况下,一个过程将会产生而不是被打断。
(一般来说,只有在CPU数量接近时才需要担心负载,即调度程序启动抢占任务时)。
如果我们的CPU占用75%的时间,我们不应该看到更高的平均负载吗?
关于活动模式,通过一些任务明显提高了CPU的利用率(很可能是一个很小的灵活性)并没有对其他任务的处理产生不利影响。 如果你能够隔离正在处理的交易,我希望你会看到一个新的小组在放缓,而现存的任务设置不受影响。
更新
一个常见的情况是,如果CPU的负载不会大幅度增加,那么高CPU就会触发一个(或一系列)其他任务,例如收到networking请求时,处理器将请求路由到单独的线程,单独的线程然后对其他进程进行一些asynchronous调用…. runqueue的采样会导致负载报告低于实际负载 – 但是不会随着CPU使用率线性上升 – 如果没有这个负载,触发的任务链将不会运行初始事件,并且因为它们按顺序发生(或多或less),所以运行队列不被充气。
虽然马修·伊芙的回答非常有帮助,并使我们朝着正确的方向发展,但这不完全是我们案件的行为造成的。 在我们的例子中,我们有一个使用线程池的multithreadingJava应用程序,为什么没有工作完成创build实际任务。
但是,线程所做的实际工作是短暂的,包括IO等待或同步等待。 正如Matthew在他的回答中提到的那样,操作系统对负载平均值进行采样,因此可以忽略短暂的任务。
我做了一个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(); } }
总而言之,理论上说,我们的应用程序中的线程空闲很多,然后执行短暂的工作,为什么任务没有通过负载平均计算正确采样。