Linux

CPU 使用率高但平均負載低

  • November 5, 2020

我們遇到了一個奇怪的行為,我們看到 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

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% 的忙呢?

我們是否正確解釋了我們的數據?什麼會導致這種行為?

至少在 Linux 上,平均負載和 CPU 使用率實際上是兩個不同的東西。平均負載是衡量一段時間內核心執行隊列中等待的任務數量(不僅是 CPU 時間,還有磁碟活動)。CPU 使用率是衡量 CPU 現在有多忙的指標。單個 CPU 執行緒在 100% 的一分鐘內可以“貢獻”到 1 分鐘的平均負載的最大負載是 1。具有超執行緒(8 個虛擬核心)的 4 核 CPU 全部以 100% 持續 1 分鐘將貢獻 8 1 分鐘平均負載。

通常這兩個數字具有相互關聯的模式,但你不能認為它們是相同的。您可以在 CPU 使用率接近 0% 的情況下進行高負載(例如當您有大量 IO 數據卡在等待狀態時),並且當您有一個單執行緒程序正在執行時,您可以有 1% 和 100% 的 CPU 負載全傾斜。同樣在短時間內,您可以看到 CPU 接近 100%,但負載仍低於 1,因為平均指標尚未“趕上”。

我已經看到一台伺服器的負載超過 15,000(是的,這確實不是錯字)和接近 0% 的 CPU 百分比。發生這種情況是因為 Samba 共享出現問題,並且大量客戶端開始陷入 IO 等待狀態。如果您看到沒有相應 CPU 活動的正常高負載數字,則可能是您遇到了某種儲存問題。在虛擬機上,這也可能意味著其他虛擬機在同一虛擬機主機上激烈競爭儲存資源。

高負載也不一定是壞事,大多數時候它只是意味著系統正在被充分利用,或者可能超出了它的能力(如果負載數量高於處理器核心數量)。在我曾經是系統管理員的地方,有人比 Nagios 更密切地觀察主系統上的平均負載。當負載很高時,他們會比您說 SMTP 更快地給我打電話 24/7。大多數時候實際上並沒有什麼問題,但他們將負載數字與錯誤聯繫起來,並像鷹一樣觀察它。檢查後,我的回答通常是系統只是在做它的工作。當然,這是負載超過 15000 的地方(雖然不是同一台伺服器),所以有時它確實意味著有問題。您必須考慮系統的用途。如果它是主力,那麼預計負載自然會很高。

負載是一個非常具有欺騙性的數字。帶上一粒鹽。

如果您以非常快的速度連續生成許多任務並很快完成,則執行隊列中的程序數量太少而無法為它們註冊負載(核心每五秒計算一次負載)。

考慮這個例子,在我有 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 個為一組(這會扭曲測試)。在這裡,父母總是試圖將孩子的數量保持在活動 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 時間。

因此,在您的情況下,答案實際上可能是,無論正在做什麼工作都會快速連續地產生大量任務(執行緒或程序)。

引用自:https://serverfault.com/questions/667078