如果你去问一位老运维,Linux 上最值得花时间吃透的东西是什么,十有八九不是某个花哨的配置,而是资源管理命令。它们平时安静地躺在 /usr/bin 里,一旦线上出问题,就是你能不能在一小时内定位根因的底气。我这些年见过太多同事查故障时只会反复敲 top,看到负载高就发蒙,最后恨不得重启机器。今天我把自己多年压箱底的这套命令和思路整理出来,包括 CPU、内存、磁盘IO、网络四类核心资源的观测方法,最后会用一个真实故障串起整个排查流程。无论你是刚转行后台的实习生,还是已经写了三年脚本的运维老兵,这篇文章都值得你从头看一遍。
1. 观测CPU与负载:从top开始,但不能只看top
在资源管理命令里,top 是多数人第一个接触的。真正理解它的输出,需要区分几个数字,否则很容易被表面的高负载带走。
1.1 top 输出里被误读最多的几个数字
top 第一行有三个 load average:1分钟、5分钟、15分钟的平均负载。很多人以为超过1就是满负载,这个观念在单核时代成立,在现在多核机器上完全不成立。负载是处于可运行状态和不可中断状态的平均进程数。4核 CPU 上 load average 为4,大致相当于把每个核心排队占满。所以判断负载时,要除以逻辑核心数,而不是盯着绝对值。
CPU 这一行中,us 是用户态 CPU 时间,sy 是内核态时间,ni 是 nice 调整后的低优先级时间,id 是空闲,wa 是等待 IO 的时间,hi 和 si 分别指硬中断和软中断,st 是虚拟化环境下被其他虚拟机抢占的时间。其中 wa 高说明 CPU 在等 IO 返回,但这并不等于一定是磁盘坏了,也可能是内存交换、网络存储延迟,甚至是一个挂载不正常的 NFS 目录。
这里有个常见误区:负载高就以为是 CPU 不够用。真实场景里,CPU 使用率可能很低,但 load 已经很高,因为很多进程被阻塞在了 IO 上。只看 %CPU 不看整行状态,很容易误判成扩容,白花成本。
1.2 负载高、CPU却不高的陷阱:不可中断进程与 vmstat
真实排障中常出现:uptime 显示 load 已经到 20,top 里 CPU 的 us 却只有 10%,id 也不是很高。这时要看有多少进程处于 D 状态(不可中断睡眠,通常是等待 IO)。top 状态栏的 D 进程如果长期存在,说明 IO 栈的下游没有返回。
vmstat 1 可以把这个局面打开。第一行是开机以来的平均值,后面的行才是最近1秒的实时数据。重点看以下字段:
r:运行队列长度,持续大于 CPU 核心数说明 CPU 算力吃紧。b:不可中断睡眠进程数,长期非零说明 IO 等待真实存在。wa:CPU 等待 IO 的时间百分比。si/so:换入换出页面数,长期非零说明内存压力已经传导到 swap。
一个我踩过的例子:某台业务机 load 很高,但 top 里 us、sy 都不高,wa 60%,D 状态进程三四个。我当时第一反应是磁盘故障,结果 iostat 显示某块云盘的 %util 只有 20%。后来发现是 NFS 挂载目录的一个客户端一直重试访问,网络文件系统的超时把进程拖在 D 状态。vmstat 只告诉我们“有人在等 IO”,但具体等哪个设备,需要 iostat 和进程栈一起看。
1.3 用 pidstat 和 mpstat 把定位细化到线程
top 默认只看到进程级,多线程应用光看进程级不够。pidstat -p <pid> 1 可以按进程输出 CPU 使用率;配合 -t 参数能看到每个线程。用 top -H -p <pid> 也能看线程,但 pidstat 的输出更适合脚本和时间序列分析。
mpstat -P ALL 1 会把每个 CPU 核心的使用情况单独打出来。多核机器上如果只有某一个核跑满,而其他核闲置,通常不是整体性能不够,而是单线程热点,要顺着线程找到代码里的热点。
组合用法:先用 top -H -p <pid> 找到 CPU 占用最高的线程号(十进制),再用 printf "%x\n" <线程号> 转成十六进制,去 Java 线程栈里匹配 nid=0x...。对于 C/C++ 程序,热点定位可以上 perf top,它会直接按 CPU 周期占比排序展示函数名。perf 需要 root 权限,且对内核版本有要求,但它是最后一步确认热点的最强工具。
1.4 实操技巧:让 top 进入脚本友好的批处理模式
很多人只使用 top 的交互式界面,但脚本采集时交互模式很难解析。其实 top 支持 -b -n 1,批处理模式只输出一次快照。配合 -o %CPU 可以按 CPU 使用率排序,再加 -w 512 把行宽放宽。例如:
bash复制top -b -n 1 -o %CPU -w 512 | head -30
采集结果前面加个 date 时间戳,比裸数据有用得多。这条命令也可以写进 cron,输出到文件作为故障回顾的素材。我习惯把它整理成一行别名,随时在排查时录现场。
不过要充分认识 top 在采样上的局限:-n 1 只采样一次,瞬时值不太稳定。如果要做性能基线,最好用 pidstat 或 sar,它们统计的是时间段内的均值,比单次 top 可靠。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 内存管理的真相:free 的每个数字背后都是内核策略
内存命令不多,无非 free、ps、top、cat /proc/meminfo,但每个数字都容易被误解。搞懂内核回收机制,比记住命令参数重要得多。
2.1 buff/cache 不是"已用内存",available 才是答案
free -h 的输出中,buff/cache 占了大块,很多人一看到 used 接近总内存就慌,觉得是内存泄漏。其实 buff/cache 是内核用来加速读写的内存。buff 表示块设备的缓冲,cache 表示页面缓存。这部分内存在需要时会自动释放给应用程序,并不会一直占着不放。
真正要注意的是 available 列。这是内核估算出的“在没有触发 swap 的情况下,还能分配给新进程的内存”,它已经考虑了回收 cache 的成本。只要 available 没有持续走低,而 swap 的 used 和 si/so 都不高,内存压力就不大。
我实测过在 64G 内存的机器上,cache 占了 50G,used 显示 55G,但再拉起一个 Java 应用仍然能正常分配内存,available 始终有 20G。这种情况下完全不需要去清理 cache。滥用 echo 3 > /proc/sys/vm/drop_caches 清缓存,反而会让后续读请求全部打回磁盘,造成一段时间的 IO 尖峰。
2.2 用 /proc/meminfo 验证内存去向
free 的数据其实来源于 /proc/meminfo。排查时看这个文件的字段更底层:
MemTotal:物理内存总量。MemAvailable:当前可分配给新程序的估计剩余。Buffers:块设备缓冲。Cached:页面缓存,也包括 tmpfs 的占用。SwapTotal/SwapFree:swap 总量和空闲量。
如果怀疑某个 tmpfs/shm 占用了大量内存,直接看 Cached 的变化不够精细,可以用 df -h /dev/shm 查看挂载点,再用 du -sh /dev/shm/* 找到占用文件。这也是资源管理命令里比较容易忽略的一环——很多内存问题其实是挂在 /dev/shm 下的临时文件写爆了。
2.3 定位进程真实内存占用:RSS、PSS 与 smaps
定位到具体进程时,top 里的 RES(RSS)只是一个粗略值。RSS 把进程映射的共享库全部计算进去了,两个进程共享同一份 libc,RSS 会各算一次,累加后甚至超过物理内存。更真实的指标是 PSS,它把共享页按进程数分摊。
用 smem 工具可以看到 PSS,或者直接读 /proc/<pid>/smaps,它会把每一段映射的内存属性列出来,包括 Rss、Shared、Private 等。一个快速估算法是:先按 RSS 排序找出可疑进程,再用一段 Shell 脚本算 PSS:
bash复制for pid in $(pgrep -f app_name); do
grep ^Pss /proc/$pid/smaps | awk '{sum+=$2} END {print sum/1024 " MB"}'
done
这个脚本简单但是能有效区分“看起来占用大”和“实际占用大”。关于内存泄漏,我见过很多新手靠 top 观察 RES 一直增长就下结论,其实把 cache 的增长也算进去了。正确做法是持续观察单个进程的 RES/PSS 变化,同时配合 swap 的使用情况,在业务压力跑量相同的情况下看曲线是否单调上升。
swap 的问题也值得单独说。vmstat 里的 si/so 长期大于 0,意味着内存已经不够用,正在把页面换出/换入。生产环境不建议把 vm.swappiness 直接改成 0(虽然网上很多教程这么教),因为内核在极端内存需求下可能选择直接杀进程而不是回收 cache。我通常建议先降到 10 左右观察,再根据业务类型调整。
3. 磁盘与IO:容量、负载和等待是三个不同维度
磁盘相关的常见困惑是把容量、负载和等待混在一起排查。实际上这三个维度对应完全不同的命令和排查路径。
3.1 df 满而 du 清:删除文件后空间去哪了
df -h 看的是文件系统整体使用量,du -sh 统计的是目录下“可见”的文件大小。如果 df 显示 100%,du 却发现只有几十 G,最常见的原因是删除了文件但进程仍然持有文件句柄。Linux 下文件被 unlink 后,只要句柄没关闭,空间就不会真正释放。
排查命令:
bash复制lsof | grep deleted
这会列出所有被删除但仍被进程占用的文件,重点关注 PID 对应的进程。处理方式一般是重启进程或让业务侧释放句柄。注意 lsof 输出可能很大,配合 grep 目标目录或 PATH 更快。
另外还有一种情况是挂载了重叠的文件系统,某个子目录被单独 mount 成另一个磁盘,du 从根目录看不到挂载点内的数据,这时用 mountpoint 确认。最后别忘了 inode 耗尽的问题,df 显示空间没满却无法写文件时,用 df -i 看 inode 使用率,如果达到 100%,清理大量小文件后空间才会恢复,这和容量耗尽完全两个方向。
3.2 iostat 的 %util 与 await:别把繁忙当成利用率
iostat -x 1 是看磁盘 IO 的首选。输出中的 %util 很多人当“磁盘使用率”,实际上它的定义是“采样周期内设备有 IO 请求的时间占比”。对于机械硬盘,这个值接近 100% 基本说明已经饱和;但对于支持并行命令的 SSD/NVMe,%util 到达 100% 也不一定意味着不能再接收请求,需要结合 IOPS、带宽和吞吐一起判断。
await 是平均每次 IO 请求处理时间,包括排队等待和实际处理。%util 高、await 也高,说明请求堆积;%util 不高但 await 高,更可能是单个请求本身慢,比如磁盘坏道、存储网络抖动。svctm 字段在现代存储上意义不大,很多版本已经剔除,不必纠结。
另一个容易忽略的是 avgqu-sz,它表示 IO 请求队列的平均长度。队列长度持续增加,说明存储端已经处理不过来。实际排查时我一般组合看 4 个值:r/s、w/s、%util、await,先确认读写类型,再判断是带宽瓶颈还是响应延迟。
3.3 iotop 把IO消耗者从系统级带到进程级
iostat 只能定位到物理盘,定位到具体进程需要 iotop。它和 top 类似,实时显示每个进程的磁盘读写速率。常用参数:
bash复制iotop -o -P
-o 只显示有 IO 的进程,-P 显示进程而不是线程。如果需要采样几秒后退出:
bash复制iotop -b -n 2 -o -P
批处理模式适合自动记录。iotop 依赖 root 或 CAP_SYS_ADMIN 权限,部分容器里不可用,可以用 pidstat -d 1 充当备选。
我印象很深的一次线上 IO 卡顿,iostat 显示 sda %util 95%,但所有业务进程的 IO 都不大,后来用 iotop 抓到是某个日志采集的 agent 在反复读取同一批冷数据,造成了大量无意义读 IO。没有进程级工具,这个问题很难快速定位。
4. 网络资源:ss 之外,你还应该掌握按进程抓流量的方法
网络资源管理往往被 CPU、内存、磁盘抢了风头,但它出问题时的排查路径完全不同,而且一旦网络阻塞,影响是全链路扩散的。
4.1 ss 快速定位监听端口、对端连接与状态分布
ss 是 netstat 的现代替代,输出更快、信息更全。最常用的组合:
bash复制ss -tlnp
显示所有监听中的 TCP 端口,-p 显示对应的进程 PID 和名称(需要 root)。排查端口冲突时,这个命令比 lsof -i:port 更直接。
ss -s 可以看当前 TCP 连接的状态汇总,比如 LISTEN、ESTAB、SYN-SENT、CLOSE_WAIT、TIME_WAIT 各有多少。如果 CLOSE_WAIT 成百上千,基本可以断定服务端应用没有正常关闭 socket。TIME_WAIT 很多并不是问题,它只是主动关闭方等待 2MSL 的机制,只要数量不导致端口耗尽,不需要专门调整内核参数。
用 ss 查看具体连接状态也方便:
bash复制ss -tan | awk '{print $1}' | sort | uniq -c
这条命令统计各状态数量,作为简单脚本非常好用。注意 ss -tan 会同时显示监听和已建立连接,如果想精确统计已建立连接,可以先过滤 LISTEN。
4.2 当积压的 CLOSE_WAIT 开始报警时,先看应用再调内核
最常见的网络资源告警是大量 CLOSE_WAIT。出现这种状态,说明对端已经 close 了连接,但本端应用没调用 close。调内核参数解决不了,根源在代码里:连接没有放到 finally 关闭、读取循环在某处 return 忘了释放。
另一个容易被忽视的是 accept 队列溢出。ss -lnt 输出中,LISTEN 状态的 Recv-Q 表示等待应用 accept 的连接数,Send-Q 是 listen backlog 设置值。如果 Recv-Q 经常等于或接近 Send-Q,说明应用 accept 速度跟不上新连接速度,表现为新请求超时。此时优先查应用线程数和阻塞位置,适当加大 backlog 只是临时缓解。
4.3 使用 iftop 和 nethogs 追踪流量来源
iftop 是按连接维度展示实时带宽,类似 top 之于 CPU。它会列出本机与哪些对端通信,传输速率有多大。排查出口带宽被打满时,先用 iftop 找到对端 IP 和高流量连接,再根据端口识别业务。
nethogs 则按进程维度展示带宽占用:
bash复制nethogs eth0
需要 root 权限。当年排查某个“晚上带宽异常”问题,iftop 看到大量流量集中在一个非标准端口,nethogs 直接显示是某个备份脚本在往 NAS 推数据,时间窗口和带宽突增完全吻合,问题十分钟内定位。
需要注意 iftop 和 nethogs 在容器里通常看不到宿主机其他进程,只适合宿主机层面排查。如果服务跑在容器内,还是靠 ss 配合容器网络命名空间定位。
5. 一次"load高 + 响应慢"故障的组合排查实录
理论讲了这么多,我用一次真实故障把上面命令串成一条完整链路。那次故障的教训是:任何单一指标都容易误判,只有组合排查才能逼近真相。
5.1 故障现象与第一轮命令
某天下午,一个内部接口的 P99 延迟从 50ms 突然涨到 800ms,监控看 load average 已经在 30 左右,而集群是 8 核。第一时间我先用 uptime 和 top 确认现场:
- load average: 30, 25, 18
- us 15%,sy 5%,wa 75%,id 5%
- D 状态进程有 5 个
这个组合已经把范围压缩到 IO 等待。接下来用 vmstat 1 观察 si/so,发现数值很小,说明不是 swap 压力。用 iostat -x 1 看磁盘,等待中的那台机器 data 盘 %util 长期在 95% 以上,await 从不到 10ms 升高到 120ms,w/s 也在翻倍。
第一轮排查到这里,方向已经清晰:不是 CPU,不是内存,是磁盘 IO 被某种写负载打爆。
5.2 顺着I/O等待链路缩小范围
当时 data 盘上跑的是数据库和消息队列,我先用 iotop -o -P 抓,排除数据库的批量写入。结果排在最前面的竟然是消息队列的落盘线程,写入速率达到正常的 8 倍。用 ss -s 看网络连接,发现同一时间段内消息生产连接也暴增,进一步确认业务侧在大量推送消息。
此时我还用 pidstat -d 1 观察了相关进程的读写速率,确认不是容器之间相互干扰。再配合 ss -tlnp 定位到消息队列监听的端口和对应 PID,整个过程从“磁盘忙”一步步收窄到“某个进程在疯狂写消息文件”。
线程栈也值得看一眼。当时用 /proc/<pid>/stack 或 gdb 显示相关线程阻塞在 write 系统调用上,正好对应磁盘 IO 等待。这一步看似冗余,但能排除掉“进程在死循环计算”的误判,直接定位为 IO 型的写阻塞。
5.3 根因处理与监控补位
根因是消息消费者 lag 严重,生产者没有做背压,把磁盘 IO 打满。处理分三步:先把积压消息暂时熔断,切到备用队列;其次把消费端扩容到 3 个实例;最后才去磁盘层面,给消息队列的数据目录扩了容量并加了 IOPS 限制。
事后我重新整理监控:磁盘 IO 的 w/s、await、%util,TCP 的 CLOSE_WAIT,以及 D 状态进程数,全部加进了告警。那次教训最大的体会是,load 高只是一个结果,要结合 vmstat、iostat、iotop、ss 一层层剥到最终诱因,任何单一指标都容易误判。
6. 把资源管理命令串成习惯的几点体会
到了最后,我想说几件和命令本身关系不大、但比命令更影响排障速度的事。
6.1 建立自己的排查管线
不要等到故障发生才去想下一步敲什么命令。我习惯按固定顺序走:uptime → top → vmstat → iostat/iotop → ss。每一步都对应一个小问题:负载高不高?CPU/IO/内存谁在忙?忙的是哪个磁盘?哪个进程?网络状态有没有积压?
管线都写在本地脚本里,一条命令输出当前负载、D 状态数、磁盘 util、swap 换页率、TCP CLOSE_WAIT 数量。故障时先跑一轮,比一个个手动敲快得多。我也建议把常用命令行写成别名,但别太依赖交互式界面,批处理模式在紧急时候更稳定。
6.2 让每个命令都带上时间戳
排查过程中我吃的亏之一是:查到一半忘记刚才 vmstat 是几点采的。后来所有脚本里统一加时间戳,或者直接 date +%T 作为输出前缀。这个习惯看起来笨,但事后回顾时间线时价值极大,尤其是多台机器协同排查时,没有时间戳的日志几乎没法对齐。
6.3 事后再用 sar 补回现场
sysstat 包的 sar 是资源管理命令里最低调但最有用的一个。只要提前开启了 /etc/cron.d/sysstat,它会按时间周期自动收集 CPU、内存、磁盘、网络、负载等历史数据。故障结束后,用 sar -q、sar -r、sar -d、sar -n DEV 回看故障前 30 分钟的指标曲线,能立刻判断问题是突发还是缓慢累积。
我自己现在每台生产服务器都装 sysstat,再配合 top 批处理快照,才能把“临时现场”变成“长期可回溯的历史”。资源管理命令从来不是单个命令的堆叠,它们只有组合成体系,才能在关键时刻真正救命。
