添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

CPU 使用率的类型。除了上一节提到的用户 CPU 之外,它还包括系统 CPU(比如上下文切换)、等待 I/O 的 CPU(比如等待磁盘的响应)以及中断 CPU(包 括软中断和硬中断)等。

等待 I/O 的 CPU 使用率(以下简称为 iowait)升高,也是最常见的一个服务器性能问题。

当 iowait 升高时,进程很可能因为得不到硬件的响应,而长时间处于不可中断状态。从 ps 或者 top 命令的输出中,你可以发现它们都处于 D 状态,也就是不可中断状态 (Uninterruptible Sleep)。

top 和 ps 是最常用的查看进程状态的工具,我们就从 top 的输出开始。下面是一个 top 命令输出的示例,S 列(也就是 Status 列)表示进程的状态。从这个示例里,你可以看到 R、D、Z、S、I 等几个状态,它们分别是什么意思呢?

top - 11:10:02 up 4 days, 17:47,  1 user,  load average: 1.16, 0.93, 1.31
Tasks: 377 total,   1 running, 318 sleeping,   0 stopped,   0 zombie
%Cpu(s): 11.6 us,  7.1 sy,  0.0 ni, 81.1 id,  0.1 wa,  0.0 hi,  0.2 si,  0.0 st
KiB Mem :  8071876 total,   932000 free,  2877168 used,  4262708 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  5102952 avail Mem
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 4040 root      20   0 1942132 149508  14712 S  17.6  1.9   1035:11 kubelet
 2425 root      20   0 2542804  92828   6644 S  11.8  1.2 362:36.93 dockerd
25414 tux       20   0   51448   4224   3356 R  11.8  0.1   0:00.03 top
 3593 root      20   0  615192 340592  18524 S   5.9  4.2 381:13.83 kube-apiserver
 3870 root      20   0 10.099g  72136  13912 S   5.9  0.9 150:27.33 etcd

R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正 在等待运行。

D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般 表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。

Z 是 Zombie 的缩写,如果你玩过“植物大战僵尸”这款游戏,应该知道它的意思。它 表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。

S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件 而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。

I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件 交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有 任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升 高, I 状态的进程却不会。

当然了,上面的示例并没有包括进程的所有状态。除了以上 5 个状态,进程还包括下面这 2 个状态。

第一个是 T 或者 t,也就是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。

向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再 向它发送 SIGCONT 信号,进程又会恢复运行(如果进程是终端里直接启动的,则需要你 用 fg 命令,恢复到前台运行)。

而当你用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪 状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程 的运行。

另一个是 X,也就是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令 中看到它。

不可中断状态

看不可中断状态,这其实是为了保证进程数据与 硬件状态一致,并且正常情况下,不可中断状态在很短时间内就会结束。所以,短时的不 可中断状态进程,我们一般可以忽略。

但如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出 现大量不可中断进程。这时,你就得注意下,系统是不是出现了 I/O 等性能问题。

僵尸进程,这是多进程应用很容易碰到的问题。正常情况下,当一个进程创建了子进 程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源; 而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。

如果父进程没这么做,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。

通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。

一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建,所以这种情况一定要避免。

不可中断状态,表示进程正在跟硬件交互,为了保护进程数据和硬件的一致性,系统不 允许其他进程或中断打断这个进程。进程长时间处于不可中断状态,通常表示系统有 I/O 性能问题。

僵尸进程表示进程已经退出,但它的父进程还没有回收子进程占用的资源。短暂的僵尸 状态我们通常不必理会,但进程长时间处于僵尸状态,就应该注意了,可能有应用程序 没有正常处理子进程的退出。

iowait 分析

一提到 iowait 升高,你首先会想要查询系统的 I/O 情况,这里我们使用dstat命令,观察cpu和I/O的使用情况

# 间隔1s输出10组数据
$ dstat 1 10
You did not select any stats, using -cdngy by default.
--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read  writ| recv  send|  in   out | int   csw
 12   7  81   0   0|  49k  637k|   0     0 |   0     0 |5928    21k
 11   7  82   0   0|   0     0 |  17k   10k|   0     0 |5108    19k
 22  15  64   0   0|   0   240k|  48k   56k|   0     0 |8880    37k
  6   3  90   0   0|   0     0 | 104k  105k|   0     0 |3598    12k
  8   2  90   0   0|   0  7376k| 105k   69k|   0     0 |4601    12k
  8   2  90   0   0|   0    24k|  17k  219k|   0     0 |4091    13k
 19  14  68   0   0|   0   168k|  48k   65k|   0     0 |8177    32k
 11   7  82   0   0|   0    12k|9923B   33k|   0     0 |5259    19k
  7   3  90   0   0|   0    56k|  23k   32k|   0     0 |3815    13k
 13   4  83   0   0|   0  2112k| 121k  840k|   0     0 |4505    17k

dstat 的输出,我们可以看到,每当 iowait 升高(wai)时,磁盘的读请求(read)都 会很大。这说明 iowait 的升高跟磁盘的读请求有关,很可能就是磁盘读导致的。

我们继续在刚才的终端中,运行 top 命令,观察 D 状态的进程,找出D状态进程的PID。

接着,我们查看这些进程的磁盘读写情况。对了,别忘了工具是什么。一般要查看某一个进程的资源使用情况,都可以用我们的老朋友 pidstat,不过这次记得加上 -d 参数,以便 输出 I/O 使用情况。

也可以使用 pidstat观察所有进程的I/O使用情况。

# 间隔1s输出多组数据
$ pidstat -d 1 20
11:34:32 AM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
11:34:33 AM     0       220     -1.00     -1.00     -1.00       1  jbd2/sda7-8
11:34:33 AM     0       6080  32768.00     0.00      0.00    170  app

从上面中的命令中,找出读写磁盘大的进行分析,下面使用stress进行分析,

然后在终端中运行 strace 命令,并用 -p 参数指定 PID 号

$ strace -p 6082 
strace: attach: ptrace(PTRACE_SEIZE, 6082): Operation not permitted

已经是root用户了,为什么还提示权限不足呢,一般遇到这种问题时,我会先检查一下进程的状态是否正常。比如,继续在终端中运行 ps 命令,并使用 grep 找出刚才的 6082 号进程:

$ ps aux | grep 6082 
root 6082 0.0 0.0 0 0 pts/0 Z+ 13:43 0:00 [app] <defunct>

果然,进程 6082 已经变成了 Z 状态,也就是僵尸进程。僵尸进程都是已经退出的进程, 所以就没法儿继续分析它的系统调用。

在终端中运行 perf record,持续一会儿(例如 15 秒),然后按 Ctrl+C 退出,再运行 perf report 查看报告:

  $ perf record -g 
  $ perf report

接着,找到我们关注的 app 进程,按回车键展开调用栈,

这个图里的 swapper 是内核中的调度进程,你可以先忽略掉。

app 的确在通过系统调用 sys_read() 读取数据。并且 从 new_sync_read 和 blkdev_direct_IO 能看出,进程正在对磁盘进行直接读,也就是绕 过了系统缓存,每个读请求都会从磁盘直接读,这就可以解释我们观察到的 iowait 升高 了。

既然僵尸进程是因为父进程没有回收子进程的资 源而出现的,那么,要解决掉它们,就要找到它们的根儿,也就是找出父进程,然后在父进程里解决。

可以使用 pstree -aps $pid进行排查,-a 表示输出命令行选项 , s 表示指定进程的父进程。