环境准备
预先安装 docker、sysstat、dstat 等工具,如 apt install docker.io dstat sysstat
操作和分析
top 和 ps 是最常用的查看进程状态的工具,我们就从 top 的输出开始。下面是一个 top 命令输出的示例,S 列(也就是 Status 列)表示进程的状态。从这个示例里,你可以看到 R、D、Z、S、I 等几个状态,它们分别是什么意思呢?
R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。
Z 是 Zombie 的缩写,如果你玩过“植物大战僵尸”这款游戏,应该知道它的意思。它表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。
查看磁盘分区:
df -h
devtmpfs 1.8G 0 1.8G 0% /dev
tmpfs 1.8G 0 1.8G 0% /dev/shm
tmpfs 1.8G 636K 1.8G 1% /run
tmpfs 1.8G 0 1.8G 0% /sys/fs/cgroup
/dev/vda1 40G 19G 19G 50% /
tmpfs 365M 0 365M 0% /run/user/0
运行案例应用:
docker run --privileged --name=app -itd feisky/app:iowait /app -d /dev/vda1 -s 20971520 -c 1
然后,输入 ps 命令,确认案例应用已正常启动。如果一切正常,你应该可以看到如下所示的输出:
ps aux | grep /app
多个 app 进程已经启动,并且它们的状态分别是 Ss+ 和 D+。其中,S 表示可中断睡眠状态,D 表示不可中断睡眠状态
top 看一下系统的资源使用情况:
平均负载( Load Average),过去 1 分钟、5 分钟和 15 分钟内的平均负载在依次减小,说明平均负载正在升高;而 1 分钟内的平均负载已经达到系统的 CPU 个数,说明系统很可能已经有了性能瓶颈。
僵尸进程比较多,而且还在不停增加,说明有子进程在退出时没被清理。
用户 CPU 和系统 CPU 都不高,但 iowait 是 48.7,好像有点儿不正常。
问题再汇总一下,就可以得到很明确的两点:
第一点,iowait 太高了,导致系统的平均负载升高,甚至达到了系统 CPU 的个数。
第二点,僵尸进程在不断增多,说明有程序没能正确清理子进程的资源。
dstat查看 CPU 和 I/O 这两种资源的使用情况
dstat 1 10
iowait 升高(wai)时,磁盘的读请求(read)都会很大。这说明 iowait 的升高跟磁盘的读请求有关,很可能就是磁盘读导致的
这时候就在想这里的磁盘读写很大是不是跟TOP命令里面app进程有关系
此时没有任何的读写,说明问题不是进程导致的。
pidstat -d 1 20
使用这个命令就能看出app在读写磁盘,app 进程到底在执行啥 I/O 操作呢?进程想要访问磁盘,就必须使用系统调用,所以接下来,就是找出 app 进程的系统调用了。
strace 正是最常用的跟踪进程系统调用的工具。 pidstat 的输出中拿到进程的 PID 号
strace -p 6082
命令报出的错误是没有权限
一般遇到这种问题时,我会先检查一下进程的状态是否正常。比如,继续在终端中运行 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 状态,也就是僵尸进程。僵尸进程都是已经退出的进程,所以就没法儿继续分析它的系统调用。
系统 iowait 的问题还在继续,但是 top、pidstat 这类工具已经不能给出更多的信息了
用 perf top 看看有没有新发现。再或者,可以像我一样,在终端中运行 perf record,持续一会儿(例如 15 秒),然后按 Ctrl+C 退出,再运行 perf report 查看报告:
perf record -g
perf report
app 通过系统调用 sys_read() 读取数据。并且从 sync_read 和 blkdev_direct_IO 能看出,进程正在对磁盘进行直接读,也就是绕过了系统缓存,每个读请求都会从磁盘直接读,这就可以解释我们观察到的 iowait 升高了。
下面的问题就容易解决了。我们接下来应该从代码层面分析,究竟是哪里出现了直接读请求。查看源码文件 app.c,你会发现它果然使用了 O_DIRECT 选项打开磁盘,于是绕过了系统缓存,直接对磁盘进行读写。
我们接下来应该从代码层面分析,究竟是哪里出现了直接读请求。使用了 O_DIRECT 选项打开磁盘,于是绕过了系统缓存,直接对磁盘进行读写。
open(disk, O_RDONLY|O_DIRECT|O_LARGEFILE, 0755)
总结性能分析思路
先用TOP命令查看有什么问题,比如iowait高,D状态的进程多,然后使用不同工具查看不同问题,比如iowait高那就跟磁盘读写有关,用dstat或者pidstat查看,用perf查看进程调用的系统函数