问题几乎总是同一个:夜里网站要二十秒才响应,到了早上一切又自己好了。此刻已经没什么可看——top 回答的是当前这一秒,而人们问的是凌晨三点。下面讲的是负载数字究竟意味着什么、其中哪些必须成对来读,以及为什么一次性的测量误导的时候多于帮忙的时候。
Load average:三个数字和一个常见的误解
uptime
cat /proc/loadavg
nproc
这三个数字是一分钟、五分钟和十五分钟的平均值。它们不该与零比较,而应与 nproc 给出的核心数比较。八核机器上的 8 是满载但健康的负载:工作量恰好是服务器能完成的量。单核 VPS 上的 2 则是长度为处理能力两倍的队列,每个请求都要排队等候。
三个数字之间的关系给出方向。一分钟值明显高于十五分钟值,说明负载此刻正在上升;反过来说明峰值已经过去,你看到的是它的尾巴。
接下来是最常把这些数字读错的那个误解。在 Linux 上,load average 并不是「处理器利用率」。与其他 Unix 系统不同,Linux 把它计入的不仅是正在运行或准备运行的进程,还包括处于 D 状态的进程——不可中断睡眠。也就是在等待磁盘或网络文件系统的进程。负载为 12 而处理器几乎空闲的服务器就是这么来的:工作没有推进,大家都在排队。
处理器还是磁盘
把这两种情况区分开是第一件值得做的事:
vmstat 1 5
iostat -x 1 3
vmstat 的输出里有三列要紧。r 是有多少进程在排队等处理器,b 是有多少被输入输出阻塞,wa 则是处理器空闲等待磁盘所占的时间比例。在 us 和 sy 都不高的情况下 wa 持续高于 10–15 %,说明服务器卡在磁盘上:加核心没有意义,现有的核心本来就闲着。
具体哪些进程在等,可以按名字列出来:
ps -eo state,pid,comm,wchan:30 | awk '$1 ~ /^D/'
一个来自我们自己实践的例子。使用默认配置时,Suricata 会写入三十来种事件类型,并且不为它们设置任何轮转——在一台运行中的服务器上,这两天就产生了 15 GB 日志。当时负载并没有表现为处理器繁忙,而恰恰表现为 I/O wait:系统不停地写,其余一切都排在它后面。解决办法不是更大的套餐,而是更短的事件类型清单和配置好的轮转。
相反的情形,纯粹与处理器有关,也远没有那么明显。一个网页以 PHP-FPM 运行所用的用户身份调用了 apt。root 在 /var/cache/apt/pkgcache.bin 保存着一份二进制缓存——在有十来个软件源的主机上是 70 MB——并且可以零代价地把它映射进内存。普通用户无权写入那个目录,于是每次调用都要重新构建缓存。在同一台机器上测得:以 root 身份是 0.01 秒处理器时间,以无特权用户身份是 4.2 秒。同一条命令,相差四百倍,再乘以每一次页面打开。
两个例子的结论是一样的:负载要测量,而不是猜测。逐条给可疑命令计时要花半小时,而且通常指向的地方与最初的怀疑并不相同。
内存:free 显示的并不是看上去那样
free -h
used 这一列本身说明不了多少问题,free 这一列则干脆具有误导性:Linux 把未使用的内存交给页缓存,应用一提出要求就还回去。该读的是 available 这一列——在不动用 swap 的前提下还能占用多少。buff/cache 很大不是问题,而是系统按预期工作的标志。
对 swap 来说,重要的不是当前数值而是形状。涨上去又回到零:出现过一次短暂的峰值。涨上去一次就停在那里:峰值早已发生,页面被换出而没有人再把它们换回来——服务器看着平静,尽管在某个时刻内存确实不够用了。交换此刻是否正在进行,可以看 vmstat 里的 si 和 so 两列;那里的非零值正是用户感受最明显的那种变慢。
如果在夜间的低谷里某个进程干脆消失了,解释通常在这里:
journalctl -k --since yesterday | grep -i "out of memory"
dmesg -T | grep -i "killed process"
形如 Out of memory: Killed process 1234 (mysqld) 的一行,比任何图表都更能了结这个问题:数据库不是「自己倒下的」,是内核在内存耗尽时把它停掉了。
是谁造成的
ps aux --sort=-%cpu | head -10
ps aux --sort=-%mem | head -10
一点保留:ps 里的 %CPU 是进程整个生命期的平均值,因此短暂的爆发会在这份列表里消失。要看它就得用 top 或 pidstat 1 5,它们按时间区间统计。
负载什么时候变成安全问题
夜里平直的 100 % 处理器占用,外加一个名字毫无意义、工作目录在 /tmp 或 /dev/shm 的进程,这是挖矿程序的经典图景,而不是网站长大了。入站流量的尖峰伴随日志写入增加,说明密码攻击正在进行。日志体积突然暴涨,通常是某个过滤器的误报风暴。
防护工具本身自成一类。在我们的一台服务器上,完全没有访客时负载仍维持在三左右,而按累计处理器时间排在 ps 最前面的既不是网站也不是数据库,而是 CrowdSec、fail2ban、Falco 和 Suricata。这既不是故障,也不是关掉它们的理由,但防护的代价值得用数字了解清楚:在小型 VPS 上是能感觉到的。
观察的意义
以上所有内容回答的都是「现在正在发生什么」。早上的那个问题——凌晨三点发生了什么——这些命令覆盖不了:如果没有人记录,过去那一夜就没有数据。而为了一台 VPS 就搭起 Prometheus 加 Grafana 也值得怀疑,因为观察用的那一套比被观察的服务器还重。
每五分钟往数据库里写一行,再加上一个据此绘出最近二十四小时的页面,就足够了:load average、处理器利用率与单列的 I/O wait、内存与 swap、磁盘读写、分区占用、inode、文件描述符、连接数。放在同一条时间轴上,成对的指标用肉眼就能读出来:处理器平静而 wa 很高、再也没有回到零的 swap、逼近上限的描述符——即将出现的 too many open files 错误在发生前几个小时就看得见了。这些东西汇总起来是什么样子,下面的演示页面可以看到。