黑客与 / - Linux 故障排除,第一部分:高负载

作者:Kyle Rankin

本专栏是致力于我最喜欢的主题之一:故障排除的系列专栏中的第一篇。我白天是一名系统管理员,虽然我喜欢我工作的很多方面,但是没有什么比得上追踪复杂的服务器问题时,以美元计算停机时间的那种肾上腺素飙升的感觉。虽然确实停机的原因和 Linux 文本编辑器一样多,故障排除的方法也同样多,但多年来,我发现我执行相同的步骤来隔离问题。因为我的专栏通常更侧重于技巧和窍门,而不是哲学和设计,所以我不会过多谈论解决问题的总体方法。相反,在本系列中,我将描述您可能在 Linux 系统上发现的一些常见问题类型,然后我将讨论如何使用常用工具(其中大多数可能已在您的系统上)来隔离和解决每类问题。

在第一篇专栏中,我从您在 Linux 系统上最常遇到的问题之一开始。不,这不是让打印工作。我指的是一台运行缓慢的服务器,它可能负载很高。但在我解释如何诊断和修复高负载之前,让我们退后一步,讨论一下负载在 Linux 机器上意味着什么,以及如何知道何时负载过高。

运行时间和负载

当管理员提到高负载时,通常他们谈论的是 平均负载。当我诊断服务器速度缓慢的原因时,我登录系统后运行的第一个命令是 uptime

$ uptime
 18:30:35 up 365 days, 5:29, 2 users, load average: 1.37, 10.15, 8.10

如您所见,今天是我的服务器运行时间纪念日。您还可以看到我的平均负载是1.37, 10.15, 8.10。这些数字分别代表我的系统在过去 1、5 和 15 分钟内的平均负载。从技术上讲,平均负载代表在过去 1、5 或 15 分钟内必须等待 CPU 时间的平均进程数。例如,如果我当前的负载为 0,则系统完全空闲。如果我的负载为 1,则 CPU 足够繁忙,以至于一个进程不得不等待 CPU 时间。如果我的负载确实为 1,然后我生成另一个通常会占用 CPU 的进程,则我的负载应变为 2。通过平均负载,系统将很好地让您了解过去 1、5 和 10 分钟内的持续繁忙程度。

在查看平均负载时,要记住的另一个重要事项是,它没有根据系统上的 CPU 数量进行标准化。一般来说,持续负载为 1 表示系统上的一个 CPU 被占用。简而言之,这意味着负载为 1 的单 CPU 系统与负载为 4 的四 CPU 系统大致一样繁忙。所以在上面的例子中,假设我有一个单 CPU 系统。如果我登录并看到上面的平均负载,我可能会认为服务器在过去 15 分钟内负载相当高 (8.10),大约在 5 分钟前达到峰值 (10.15),但最近,至少在过去 1 分钟内,负载已显着下降。如果我看到这种情况,我甚至可能会认为负载的真正原因已经消退。另一方面,如果平均负载为 20.68、5.01、1.03,我会得出结论,高负载很可能是在过去 5 分钟内开始的,并且正在变得更糟。

多高才算高?

在您了解平均负载的含义后,下一个合乎逻辑的问题是“什么样的平均负载是好的,什么样的平均负载是坏的?” 答案是“视情况而定”。您看,很多不同的事情都可能导致负载过高,每种情况对性能的影响都不同。一台服务器的负载可能是 50,但仍然响应迅速,而另一台服务器的负载可能是 10,但登录却需要很长时间。我曾遇到过平均负载达到数百的服务器,它们当然很慢,但没有崩溃,我还遇到过一台服务器,其平均负载始终为 50,但仍然响应迅速,并且运行了多年。

当您对负载过高的系统进行故障排除时,真正重要的是 原因 负载过高。当您开始诊断高负载时,您会发现大多数负载似乎都属于三个类别:CPU 密集型负载、内存不足问题引起的负载和 I/O 密集型负载。我在下面详细解释了这些类别中的每一个,以及如何使用 top 和 iostat 等工具来隔离根本原因。

top

如果我登录运行缓慢的系统时使用的第一个工具是 uptime,那么我使用的第二个工具是 top。top 的优点在于它适用于所有主要的 Linux 系统,并且它在单个屏幕中提供了大量有用的信息。top 是一个非常复杂的工具,有很多选项,可以单独写一篇文章来介绍它。在本专栏中,我将重点介绍如何解释其输出来诊断高负载。

要使用 top,只需键入top在命令行中。默认情况下,top 将以交互模式运行,并每隔几秒钟更新其输出。列表 1 显示了终端中的 top 示例输出。

列表 1. top 示例输出

top - 14:08:25 up 38 days, 8:02, 1 user, load average: 1.70, 1.77, 1.68
Tasks: 107 total,   3 running, 104 sleeping,   0 stopped,   0 zombie
Cpu(s): 11.4%us, 29.6%sy, 0.0%ni, 58.3%id, .7%wa, 0.0%hi, 0.0%si, 0.0%st
Mem:   1024176k total,   997408k used,    26768k free,    85520k buffers
Swap:  1004052k total,     4360k used,   999692k free,   286040k cached

  PID USER    PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 9463 mysql   16   0  686m 111m 3328 S   53  5.5 569:17.64 mysqld
18749 nagios  16   0  140m 134m 1868 S   12  6.6   1345:01 nagios2db_status
24636 nagios  17   0 34660  10m  712 S    8  0.5   1195:15 nagios
22442 nagios  24   0  6048 2024 1452 S    8  0.1   0:00.04 check_time.pl

如您所见,仅几行就包含了很多信息。第一行镜像了您从 uptime 命令获得的信息,并将每隔几秒钟更新一次最新的平均负载。在本例中,您可以看到我的系统很忙,但不能称之为负载过重。尽管如此,此输出很好地分解为我们的不同负载类别。当我对运行缓慢的系统进行故障排除时,我通常会按顺序排除 CPU 密集型负载、RAM 问题,最后是 I/O 问题,所以让我们从 CPU 密集型负载开始。

CPU 密集型负载

CPU 密集型负载是指当您同时运行太多 CPU 密集型进程时引起的负载。由于每个进程都需要 CPU 资源,因此它们都必须轮流等待。要检查负载是否为 CPU 密集型,请检查 top 输出中的 CPU 行

Cpu(s): 11.4%us, 29.6%sy, 0.0%ni, 58.3%id, .7%wa, 0.0%hi, 0.0%si, 0.0%st

这些百分比中的每一个都是 CPU 时间的百分比,用于执行特定任务。同样,您可以花一整篇文章来介绍 top 的所有输出,所以这里介绍其中一些值以及如何读取它们

  • us:用户 CPU 时间。通常,当您遇到 CPU 密集型负载时,这是由于系统上用户运行的进程引起的,例如 Apache、MySQL 或许是 shell 脚本。如果此百分比很高,则用户进程(如这些进程)很可能是负载的原因。

  • sy:系统 CPU 时间。系统 CPU 时间是内核和其他系统进程占用的 CPU 百分比。CPU 密集型负载应表现为用户或系统 CPU 时间的百分比很高。

  • id:CPU 空闲时间。这是 CPU 空闲的时间百分比。此处的数字越高越好!事实上,如果您看到非常高的 CPU 空闲时间,则很好地表明任何高负载都不是 CPU 密集型的。

  • wa:I/O 等待。I/O 等待值表示 CPU 花费在等待 I/O(通常是磁盘 I/O)的时间百分比。如果您负载很高且此值很高,则负载很可能不是 CPU 密集型的,而是由于 RAM 问题或高磁盘 I/O 引起的。

追踪 CPU 密集型负载

如果您确实看到用户或系统列中的百分比很高,则很可能您的负载是 CPU 密集型的。要追踪根本原因,请向下跳几行到 top 显示系统上当前正在运行的进程列表的位置。默认情况下,top 将根据 CPU 使用百分比对这些进程进行排序,CPU 使用率最高的进程位于顶部(列表 2)。

列表 2. 当前进程示例

  PID USER   PR NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 9463 mysql  16  0  686m 111m 3328 S   53  5.5 569:17.64 mysqld
18749 nagios 16  0  140m 134m 1868 S   12  6.6   1345:01 nagios2db_status
24636 nagios 17  0 34660  10m  712 S    8  0.5   1195:15 nagios
22442 nagios 24  0  6048 2024 1452 S    8  0.1   0:00.04 check_time.pl

%CPU 列告诉您每个进程占用的 CPU 量。在本例中,您可以看到 MySQL 占用了我 53% 的 CPU。当您在 CPU 密集型负载期间查看此输出时,您可能会看到两种情况之一:要么您会有一个进程占用 99% 的 CPU,要么您会看到许多较小的进程都在争夺 CPU 时间的百分比。在任何一种情况下,相对来说都容易看到导致问题的进程。关于 CPU 密集型负载,我最后想补充一点:我见过系统负载非常高,仅仅是因为一个多线程程序在没有太多 CPU 的系统上生成了大量线程。如果您在单 CPU 系统上生成 20 个线程,您可能会看到平均负载很高,即使没有任何特定的进程似乎占用 CPU 时间。

内存不足问题

高负载的下一个原因是系统已耗尽可用 RAM 并已开始进入交换空间。由于交换空间通常位于比 RAM 慢得多的硬盘驱动器上,因此当您用完可用 RAM 并进入交换空间时,每个进程都会随着磁盘的使用而急剧减速。通常,这会导致恶性循环,因为已交换的进程运行速度较慢,响应时间更长,并导致更多进程堆积,直到系统耗尽 RAM 或速度绝对慢下来。交换问题的棘手之处在于,由于它们对磁盘的冲击如此之大,因此很容易将它们误诊为 I/O 密集型负载。毕竟,如果您的磁盘被用作 RAM,则任何实际上想要访问磁盘上文件的进程都必须排队等待。因此,如果我在 top 的 CPU 行中看到高 I/O 等待,我会接下来检查 RAM 并排除它,然后再对任何其他 I/O 问题进行故障排除。

当我想诊断内存不足问题时,我首先查看 top 输出中的接下来几行

Mem: 1024176k total, 997408k used, 26768k free, 85520k buffers
Swap: 1004052k total, 4360k used, 999692k free, 286040k cached

这些行告诉您 RAM 和交换的总量以及已用和空闲量;但是,请仔细查看,因为这些数字可能会产生误导。我见过许多新手甚至有经验的管理员查看上面的输出并得出结论,系统几乎耗尽了 RAM,因为只有 26768k 空闲。虽然这确实显示了当前有多少 RAM 未使用,但它并没有说明全部情况。

Linux 文件缓存

当您访问文件并且 Linux 内核将其加载到 RAM 中时,当您不再需要该文件时,内核不一定会卸载该文件。如果有足够的可用 RAM,内核会尝试将尽可能多的文件缓存到 RAM 中。这样,如果您第二次访问该文件,内核可以从 RAM 而不是磁盘检索它,从而提供更好的性能。随着系统持续运行,您会发现空闲 RAM 实际上会显得相当小。但是,如果进程需要更多 RAM,内核只需使用其文件缓存中的一些。事实上,我看到很多超频爱好者想要提高性能并创建一个 ramdisk 来存储他们的文件。他们没有意识到的是,通常情况下,如果他们只是让内核为他们完成工作,他们可能会看到更好的结果,并更有效地利用他们的 RAM。

要获得更准确的可用 RAM 量,您需要将 free 列中的值与 cached 列中的值结合起来。在我的示例中,我将有 26768k + 286040k,或超过 300Mb 的可用 RAM。在这种情况下,我可以安全地假设我的系统没有遇到内存不足问题。当然,即使是可用 RAM 非常少的系统也可能没有进入交换空间。这就是为什么您还必须检查 Swap: 行,看看是否使用了很高比例的交换空间。

追踪高 RAM 使用率

如果您确实发现您的可用 RAM 不足,请返回到 top 的相同进程输出,只是这次查看 %MEM 列。默认情况下,top 将按 %CPU 列排序,因此只需键入 M,它将重新排序以显示哪些进程正在使用最高百分比的 RAM。在列表 3 的输出中,我按 RAM 对相同的进程进行了排序,您可以看到 nagios2db_status 进程使用的最多,为 6.6%。

列表 3. 按 RAM 排序的进程

  PID USER   PR NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
18749 nagios 16  0  140m 134m 1868 S   12  6.6   1345:01 nagios2db_status
 9463 mysql  16  0  686m 111m 3328 S   53  5.5 569:17.64 mysqld
24636 nagios 17  0 34660  10m  712 S    8  0.5   1195:15 nagios
22442 nagios 24  0  6048 2024 1452 S    8  0.1   0:00.04 check_time.pl
I/O 密集型负载

有时,I/O 密集型负载可能很难追踪。正如我之前提到的,如果您的系统正在交换,则可能会使负载看起来是 I/O 密集型的。但是,一旦您排除了交换,如果您确实有高 I/O 等待,下一步是尝试追踪哪个磁盘和分区正在获得大部分 I/O 流量。为此,您需要像 iostat 这样的工具。

像 top 一样,iostat 工具也是一个复杂且功能齐全的工具,可以写一篇文章来专门介绍它。与 top 不同,虽然 iostat 工具应该适用于您的发行版,但默认情况下可能未安装在您的系统上,因此您需要追踪哪个软件包提供了它。在基于 Red Hat 和 Debian 的系统中,您可以在 sysstat 软件包中获得它。安装完成后,只需运行不带参数的 iostat 即可获得磁盘 I/O 统计信息的良好总体视图

Linux 2.6.24-19-server (hostname) 	01/31/2009

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           5.73    0.07    2.03    0.53    0.00   91.64

Device:    tps  Blk_read/s  Blk_wrtn/s   Blk_read   Blk_wrtn
sda       9.82       417.96        27.53   30227262    1990625
sda1      6.55       219.10         7.12   15845129     515216
sda2      0.04         0.74         3.31      53506     239328
sda3      3.24       198.12        17.09   14328323    1236081

与 top 一样,iostat 为您提供了 CPU 百分比输出。在其下方,它提供了系统上每个驱动器和分区的细分,以及每个驱动器和分区的统计信息

  • tps:每秒事务数。

  • Blk_read/s:每秒读取的块数。

  • Blk_wrtn/s:每秒写入的块数。

  • Blk_read:读取的总块数。

  • Blk_wrtn:写入的总块数。

通过查看这些不同的值并将它们相互比较,理想情况下,您将能够首先找出哪个分区(或哪些分区)正在获得大部分 I/O 流量,其次,大部分流量是读取 (Blk_read/s) 还是写入 (Blk_wrtn/s)。正如我所说,追踪 I/O 问题的根源可能很棘手,但希望这些值将帮助您隔离哪些进程可能导致负载。

例如,如果您有 I/O 密集型负载,并且您怀疑您的远程备份作业可能是罪魁祸首,请比较读取和写入统计信息。因为您知道远程备份作业主要会从您的磁盘读取数据,所以如果您看到大部分磁盘 I/O 是写入,您可以合理地假设它不是来自备份作业。另一方面,如果您确实看到特定分区上有大量的读取 I/O,您可以运行 lsof 命令并 grep 备份进程,看看它是否确实在该分区上有一些打开的文件句柄。

如您所见,使用 iostat 追踪 I/O 问题并非易事。即使没有参数,也可能需要一些时间和经验才能理解输出。也就是说,iostat 确实有很多参数,您可以使用这些参数来获取有关不同类型 I/O 的更多信息,包括查找有关 NFS 共享详细信息的模式。如果您想了解更多信息,请查看 iostat 的手册页。

直到最近,像 iostat 这样的工具还是系统管理员工具箱中用于追踪 I/O 问题的极限工具,但由于内核的最新发展,查找每个进程级别的 I/O 原因变得更容易了。如果您有一个相对较新的系统,请查看 iotop 工具。与 iostat 一样,它可能默认未安装,但顾名思义,它的作用本质上类似于 top,只是用于磁盘 I/O。在列表 4 中,您可以看到此计算机上的 rsync 进程正在使用最多的 I/O(在本例中为读取 I/O)。

列表 4. iotop 工具输出示例

Total DISK READ: 189.52 K/s | Total DISK WRITE: 0.00 B/s
  TID  PRIO  USER DISK READ DISK WRITE  SWAPIN     IO>    COMMAND          
 8169 be/4 root  189.52 K/s   0.00 B/s  0.00 %  0.00 % rsync --server --se
 4243 be/4 kyle    0.00 B/s   3.79 K/s  0.00 %  0.00 % cli /usr/lib/gnome-
 4244 be/4 kyle    0.00 B/s   3.79 K/s  0.00 %  0.00 % cli /usr/lib/gnome-
    1 be/4 root    0.00 B/s   0.00 B/s  0.00 %  0.00 % init
一旦追踪到罪魁祸首

您如何处理这些导致负载的进程取决于您自己,并且取决于许多因素。在某些情况下,您可能有一个失控的脚本,您可以轻松地将其杀死。在其他情况下,例如数据库进程,简单地杀死该进程可能并不安全,因为它可能会留下损坏的数据。此外,这可能只是您的服务正在耗尽容量,真正的解决方案是向您当前的服务器添加更多资源,或添加更多服务器来分担负载。甚至可能是来自机器上正在运行的一次性作业的负载,并且不应在将来影响负载,因此您只需让该进程完成即可。由于导致进程占用服务器资源的原因有很多,因此很难在此处一一列出,但希望能够识别高负载的原因将使您在下次收到机器速度缓慢的警报时走上正确的轨道。

Kyle Rankin 是旧金山湾区的系统架构师,也是许多本书的作者,包括 The Official Ubuntu Server BookKnoppix HacksUbuntu Hacks。他目前是 North Bay Linux Users' Group 的主席。

加载 Disqus 评论