线上机器一旦变慢,最怕的不是问题复杂,而是人一慌就开始乱猜。真正有效的排查,不是上来盯着某个进程猛看,而是先拿到全局快照,再把异常收敛到 CPU、内存、磁盘 IO 或网络中的一条主线,最后定位到具体进程、线程、系统调用或连接状态。这篇指南讲的不是零散命令,而是一套真正能落地的排查顺序——从 PSI 压力指标到容器环境,从 sar 历史回溯到级联故障识别,覆盖现代 Linux 运维排查的完整链路。
监控上显示,接口延迟从 50ms 突然涨到 3s,错误率开始抬头,机器 CPU 告警。你连上服务器,终端光标一闪一闪,脑子里却不一定立刻有答案。很多人这时候会直接猜:是不是流量暴涨了,是不是数据库慢了,是不是有人把日志开成了 DEBUG,是不是内存泄漏了。
猜测当然有时会猜中,但大部分线上排查真正浪费时间的地方,恰恰就是“太早下结论”。
服务器性能问题,表面上看都是“变慢了”,但底层成因并不一样。CPU 打满会变慢,内存回收压力大也会变慢,磁盘排队会变慢,网络链路抖动还是会变慢。更麻烦的是,这几类问题在监控上经常会互相伪装。比如磁盘 IO 排队,会把 load average 拉高;内存紧张导致频繁回收,也可能表现成 CPU 抖动;网络发送缓冲堆积,又可能看起来像应用线程卡住。
所以,线上排查最重要的不是“我会多少命令”,而是“我能不能先判断主矛盾在哪一层”。

线上机器出问题以后,第一反应应该是“先拍快照”,而不是“先判断根因”。
这里的所谓全局视角,核心就是两件事:
如果这一步做对了,后面的排查会越来越窄;如果这一步做错了,后面的努力大概率是在错误方向上深挖。
top很多人收到报警后第一条命令就是 top,这没错,但如果只看 top,信息是不够的。
top 能回答“当前谁最忙”,却不一定能回答“为什么忙”。一个进程 CPU 很高,可能真的是业务计算吃满;也可能只是别的资源拖住了系统,导致这个进程看起来异常活跃。更关键的是,load average 并不等于 CPU 使用率。Linux 会把可运行任务和不可中断等待任务一起计入负载,所以磁盘 IO 排队同样会把负载顶上去。
这就是为什么第一组命令最好不要只有一个 top,而应该是下面这套:
dateuptimetop -b -n 1 | head -n 25vmstat 1 5iostat -x -y 1 5ss -s这组命令的价值,不在于“全面”,而在于它们互相补位。
date 用来记录故障时刻,后面要对日志、监控、链路追踪时很有用。uptime 快速看负载是否突然抬高。top 看进程层面的忙碌情况。vmstat 看 CPU、内存、换页、块 IO 是否同步异常。iostat 判断磁盘是否真的在排队。ss -s 看连接状态有没有明显堆积。load average 高,是 CPU 真不够,还是有大量任务在等 IO?MemAvailable 是不是真的在掉,还是只是页缓存变大了?只要把这四个问题依次问完,方向通常就不会偏。
load average | ||
%Cpuus/sy/wa/id | ||
vmstatr/b/si/so | ||
iostatawait/aqu-sz/%util | ||
ss -s |
其中最容易误判的是两个地方。
第一,load average 高,不等于 CPU 满。
第二,wa 高,也不能单独下结论说“磁盘有问题”,因为 iowait 本身并不是一个足够可靠的单指标,必须配合 iostat、vmstat 和进程级 IO 再判断。
us+syid 很低,r 也高 | ||
MemAvailablesi/so 不为 0 | ||
wab 高,await 高,aqu-sz 高 | ||
TIME-WAITCLOSE-WAIT、SYN-RECV 明显异常 |
上面的初判方法已经够用了,但如果你的内核版本在 4.20 以上,还有一个更直接的工具可以用:PSI(Pressure Stall Information)。
PSI 的核心价值在于:它不是告诉你"某个资源的使用率是多少",而是直接告诉你"有多少比例的时间,任务因为等待这个资源而被卡住了"。这比传统指标更接近业务体感。
cat /proc/pressure/cpucat /proc/pressure/memorycat /proc/pressure/io每个文件会输出类似这样的内容:
some avg10=4.50 avg60=2.80 avg300=1.20 total=18923456full avg10=0.00 avg60=0.00 avg300=0.00 total=0这里面最关键的两个概念:
some:至少有一个任务因为等待该资源而被阻塞的时间占比full:所有任务都在等待该资源、没有任何有效工作在进行的时间占比判断标准可以这样理解:
cpu some avg10 > 20 | ||
memory some avg10 > 10 | ||
memory full avg10 > 5 | ||
io some avg10 > 15 | ||
io full avg10 > 10 |
PSI 的好处是它把"资源紧张程度"量化成了一个统一的百分比,不需要你自己去交叉对比多个指标。在全局初判阶段,先看一眼 PSI,往往能比传统方法更快地锁定主矛盾方向。
需要注意的是,PSI 在某些老内核或精简内核上可能没有开启。如果 /proc/pressure/ 目录不存在,说明内核编译时没有打开 CONFIG_PSI,这时就只能回到传统方法。
线上排查有一个非常现实的困境:你赶到现场时,故障可能已经过去了。
top、vmstat、iostat 这些命令都是实时采样,它们只能告诉你"现在怎么样",但如果问题发生在 10 分钟前、半小时前甚至昨天凌晨,这些命令就无能为力了。
这时候 sar 就非常关键。sar 是 sysstat 包的一部分,它会定期把系统指标写入二进制日志文件(通常在 /var/log/sa/ 或 /var/log/sysstat/),你可以事后回溯任意时间段的数据。
# 查看今天的 CPU 使用历史(每 10 分钟一个点)sar -u# 查看今天的内存使用历史sar -r# 查看今天的磁盘 IO 历史sar -d -p# 查看今天的网络流量历史sar -n DEV# 查看今天的负载历史sar -q# 查看指定时间段(比如下午 2 点到 3 点)sar -u -s 14:00:00 -e 15:00:00# 查看昨天的数据(sa 文件按日期编号)sar -u -f /var/log/sa/sa$(date -d yesterday +%d)sar 在排查中最常用的几个场景:
一个实用技巧:如果你发现机器上 sar 没有数据,大概率是 sysstat 没有安装或者 cron 任务没有启用。建议在所有生产机器上确保 sysstat 已安装并且 /etc/cron.d/sysstat 处于激活状态。这是一个成本极低但关键时刻能救命的配置。
打开配置文件:
sudo nano /etc/default/sysstat找到 ENABLED="false" 这一行,将其修改为 true:
ENABLED="true"(或者你可以直接运行这条命令一键替换:sudo sed -i 's/ENABLED="false"/ENABLED="true"/g' /etc/default/sysstat)

很多机器报“CPU 告警”时,真正的问题未必是 CPU。
这是线上排查里非常常见的误区。因为在监控系统里,CPU 和负载通常是最早出现在大盘上的,所以大家很容易本能地朝 CPU 方向钻。但 Linux 的负载统计不是“CPU 百分比”那么简单,大量等待磁盘 IO 的任务也会进入负载。所以你看到 load average 很高时,第一反应不应该是“扩容 CPU”,而应该是“先分清这些任务到底在跑,还是在等”。
top -b -n 1 | head -n 20vmstat 1 5mpstat -P ALL 1 5ps aux --sort=-%cpu | head -n 10这四条命令分别在回答不同的问题。
top 看总量和热点进程。vmstat 看可运行队列 r、阻塞任务 b、上下文切换 cs。mpstat -P ALL 看是不是只有少数几个核被打满。ps 看哪些进程最吃 CPU。us、sy、wa 分别意味着什么很多人会背这几个缩写,但真到排查现场,不一定知道该怎么用它们判断。
us 高,说明 CPU 时间主要花在用户态,常见于业务计算、循环、序列化、压缩、正则、GC。sy 高,说明内核态开销偏大,常见于系统调用密集、网络包处理开销大、锁竞争严重、频繁 fork。wa 高,说明 CPU 有相当一部分时间在等 IO 完成,但这时不能只看 CPU 面板,必须继续结合磁盘维度看。把这三个指标分清楚,排查速度会快很多。因为它们决定了你下一步要不要继续看 CPU,还是应该转向 IO 或网络。
一般来说,下面这组信号同时出现,才更像是 CPU 本身成了主瓶颈:
us+sy 很高,id 很低vmstat 的 r 长时间明显大于核数mpstat -P ALL 显示多个核持续繁忙wa、高 b、高 si/so如果这些条件不满足,就不要急着把锅扣到 CPU 头上。
ps aux --sort=-%cpu | head -n 10top -H -p <PID>pidstat -t -u -p <PID> 1 5ps -eLo pid,ppid,tid,psr,pcpu,stat,comm --sort=-pcpu | headperf top -p <PID>strace -f -tt -T -p <PID> -c这个顺序不要乱。
先找进程,再找线程,再看函数热点,最后才去看系统调用统计。因为很多时候,问题在 perf top 那一步就已经收敛了,没必要一上来就进入最重的观察手段。
us 高,说明算力真的被业务吃掉了这种场景最典型。比如某段代码里有死循环,某个正则表达式触发了灾难性回溯,某个请求路径引入了巨大的 JSON 序列化开销,或者 Java 程序在高压下频繁 Full GC。它们的共同点是:CPU 时间主要耗在用户态,热点通常集中在少数业务线程上。
这时候最有用的命令是:
top -H -p <PID>perf top -p <PID>top -H 解决的是“哪个线程最热”,perf top 解决的是“热在哪里”。前者帮你缩小对象,后者帮你收敛到函数。
sy 高,说明开销大头在内核态这类问题比单纯的业务计算更隐蔽。
如果你看到 sy 特别高,通常应该怀疑:
futex 很多,说明锁竞争明显open / close / stat这时用 strace -c 很有效:
strace -f -tt -T -p <PID> -c如果你看到 write、recvfrom、sendto、futex、epoll_wait 这些调用占比异常高,就不要再停留在“CPU 高”这个表象上了,而要追问:为什么这些系统调用会这么密集。
这是最容易让人误判的一种情况。
表面看机器“负载爆了”,实际上真正忙的不是 CPU,而是别的资源。常见信号是:
load average 很高us+sy 没那么夸张wa 较高,或者 vmstat 里的 b 很高这时候如果你继续在 CPU 上深挖,大概率只会越查越偏。正确动作是立刻转到磁盘 IO 或锁等待链路。
load average 当成纯 CPU 指标。%CPU 高,就不再往线程层细分。mpstat -P ALL,从而错过单核瓶颈。st,误把宿主机争抢当成业务退化。内存问题看起来比 CPU 更直观,因为“内存快满了”这句话非常容易理解。但真到了 Linux 上,反而更容易因为“看起来很满”而误判。
原因很简单:Linux 会主动用空闲内存做缓存。也就是说,“内存被用掉了”本身不是问题,关键要看这部分内存到底是什么。
free,先看 availablefree -hcat /proc/meminfo | egrep 'MemTotal|MemFree|MemAvailable|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab|SReclaimable|SUnreclaim|Committed_AS|CommitLimit'vmstat 1 5ps aux --sort=-%mem | head -n 10很多人第一次看 free -h,会本能地关注 free 那一列,但线上排查时更有判断价值的是 available。
因为 available 更接近“在不明显触发交换的情况下,还能给新进程用多少”。如果 free 很低,但 available 还比较健康,同时 Cached 很大,那大概率只是页缓存正常占用了内存,并不是系统真的快撑不住了。
MemAvailablesi/so 持续非零 | ||
VmRSSRssAnon 持续上涨 | ||
Cachedavailable 还可以 | ||
SlabSUnreclaim 异常上涨 |
因为单次截图很容易骗人。
某个进程此刻 VmRSS 很大,不代表它就有泄漏。可能只是它刚完成一轮批量处理,内存还没回落;也可能是它持有大量文件映射;还可能只是运行期缓存比较多。真正值得警惕的,是“业务高峰过去了,它的 RSS 还是持续往上走,而且回不来”。
所以内存问题的判断,最好带一点时间维度:
pidstat -r -p <PID> 1 5cat /proc/<PID>/status | egrep 'VmSize|VmRSS|VmSwap|RssAnon|RssFile|RssShmem'pmap -x <PID> | tail -n 20如果还不够,再看:
grep -E '^(Size|Rss|Pss|Private|Shared|Anonymous|AnonHugePages)' /proc/<PID>/smaps | head -n 80它往往不是“瞬间爆掉”,而是慢慢涨上去。
特征通常有这些:
VmRSS 或 RssAnon 持续增长MemAvailable 越来越低si/so 会变得明显对 C/C++ 程序来说,这类问题经常和对象生命周期、容器无限增长、缓存淘汰策略失效、跨线程对象持有有关。
对 Java、Go 之类运行时语言来说,还要结合 GC 行为、堆上对象增长、逃逸、引用链去判断。
这是 Linux 新手最容易被误导的地方。
如果你看到:
Cached 很大MemAvailable 仍然不低那这通常不是问题,而是内核在正常利用内存做文件缓存。缓存本来就是为了提升 IO 命中率存在的,只有当它挤压到真正可用内存,或者引发回收压力时,才值得担心。
有时候用户态进程看起来都不算夸张,但系统可用内存仍在明显下降。这时就要怀疑是不是内核对象在涨。
可以先看:
slabtop -o如果 Slab、SUnreclaim 明显上涨,slabtop 里某类对象数量持续膨胀,那问题就未必在业务堆内存本身,而可能在 dentry、inode、socket buffer 之类的内核缓存。
这是现代线上环境里非常值得补的一层。
如果你的服务跑在 cgroup v2 下,容器本身的 memory.max、memory.high、memory.current 比宿主机总内存更关键。很多时候宿主机还没 OOM,但容器因为超过自己的上限已经被强回收甚至被杀了。
cat /sys/fs/cgroup/<cg>/memory.currentcat /sys/fs/cgroup/<cg>/memory.peakcat /sys/fs/cgroup/<cg>/memory.eventscat /sys/fs/cgroup/<cg>/memory.stat如果 memory.events 里的 oom、oom_kill 在增长,那问题已经非常明确了。
systemd-oomd不少人只会去 dmesg 里找 OOM 日志,但如果机器启用了 systemd-oomd,进程可能在传统内核 OOM 之前就被主动清理掉。
oomctl dumpdmesg -T | grep -Ei 'oom|killed process' | tail -n 20这一步的价值在于:弄清楚“到底是谁杀了它”。如果这一点都没分清,后面的分析很容易走偏。
MemFree 很低,就说机器内存不够。Cached 很大,就下结论说“内存泄漏”。
磁盘 IO 问题是最容易“伪装成 CPU 问题”的。
因为一旦块设备开始排队,任务就会进入不可中断等待,负载会上升,CPU 面板上也会出现明显的 wa。如果这时候只盯着 CPU 面板,很容易误以为“CPU 告警就是 CPU 不够”,实际上 CPU 只是在等磁盘。
vmstat 1 5iostat -x -y 1 5pidstat -d -p ALL 1 5cat /proc/meminfo | egrep 'Dirty|Writeback'这四个命令配在一起,基本能把 IO 的大方向看清楚。
vmstat 里的 b 能看到有多少任务在阻塞。iostat -x 能看到磁盘是不是在排队。pidstat -d 用来找具体哪个进程在读写。Dirty 和 Writeback 可以帮助判断页缓存回写压力。iostat -x 里最值得看的,不是带宽,而是延迟和队列很多人一看到 rkB/s、wkB/s 就开始紧张,觉得带宽很大就一定是问题。其实不是。
真正决定“业务为什么慢”的,通常不是吞吐数字本身,而是请求有没有在设备前排队。
判断这件事最有价值的三列是:
await:一次 IO 从入队到完成的平均时间aqu-sz:平均队列长度%util:设备忙碌时间占比这里尤其要强调两个判断点。
第一,await 高而且 aqu-sz 高,才说明“慢是因为排队”。
第二,%util 很高并不永远等于“磁盘一定到极限了”,特别是在现代 SSD、RAID 或更复杂的存储栈上,不能机械地只拿这一列做结论。
/proc/<PID>/io 很关键系统级看到“磁盘忙”以后,下一步一定要尽快把问题缩到进程层面。
pidstat -d -p ALL 1 5cat /proc/<PID>/iopidstat -d 可以快速找出哪个进程 kB_wr/s 或 kB_rd/s 异常高。
而 /proc/<PID>/io 更进一步,能帮你看清楚:
如果你发现 syscw 特别高,但 write_bytes 并没有高到相同比例,常常意味着写得很碎,调用频率远高于有效写入量。这种场景在日志系统上非常常见。
fsync 为什么经常是关键嫌疑人很多程序员以为“写文件慢”就是写得太多,其实线上更常见的情况是:写本身不算多,但每次写完都在同步刷盘。
这时可以用:
strace -f -tt -T -e trace=write,fsync,fdatasync -p <PID>如果你在输出里反复看到 fsync() 或 fdatasync(),那延迟高的原因就不只是“有写”,而是“每次写都在等介质确认”。数据库、日志、消息持久化模块里,这类问题尤其常见。
Linux 下大量写操作通常会先进页缓存,再由内核异步刷盘。这种设计本来是为了吞吐,但在写峰值特别高的时候,也可能积压出大量待回写脏页,最终反过来压到前台线程。
所以如果你看到:
Dirty 很高Writeback 持续不低await 也逐步抬高那就要考虑是不是页缓存和后台回写开始互相牵制了。
%util=100% 就直接断言“磁盘一定满了”。await 和 aqu-sz。网络问题看起来最杂,因为它既可能是链路问题,也可能是应用本身的连接管理问题,还可能是监听队列、socket 缓冲、DNS、对端处理能力共同作用的结果。
所以网络排查不能只看“连接多不多”,而要同时看三件事:
ss -sss -tanss -tan state establishedss -tan state time-waitss -tan state close-waitss -tiss -mss -lntTIME-WAIT 多,不一定就是故障这是网络排查里最经典的误判之一。
很多短连接服务天生就会产生大量 TIME-WAIT,这本身是 TCP 正常关闭流程的一部分。只有当它已经影响到端口资源、建连能力,或者和业务设计明显不匹配时,它才真正构成问题。
相比之下,CLOSE-WAIT 持续增长往往更值得警惕。因为它通常意味着:对端已经把连接关了,而本端没有及时 close()。这更像代码层面的连接清理问题。
ss -i 特别重要很多人网络排查停在 ss -s 或 netstat 这一层,只能看到连接数量,却看不到连接质量。
ss -i 则能直接给出一些真正有判断价值的 TCP 内部信息,比如:
rtt:往返时延rto:重传超时cwnd:拥塞窗口ssthresh:慢启动阈值如果一个接口超时,但你用 ss -i 看到连接的 rtt、rto 都明显升高,那就说明问题已经不是“连接有没有建立起来”这么简单,而是链路质量或对端处理能力已经出了明显问题。
ss -m 用来判断 socket 层是不是堆积了如果连接数量看起来不夸张,但服务仍然发不动、收不动,就应该去看 socket 内存和队列。
ss -m 里值得关注的包括:
rmem_allocwmem_allocwmem_queuedback_logsock_drop这些信息的意义在于:它能帮助你判断,问题是出在链路上,还是应用根本没来得及消费和处理。
一个端口在 LISTEN,不代表它就处理得过来。
如果服务端 accept() 太慢,或者用户态工作线程已经堆满,监听队列同样会积压。外部表现往往是:
这时要一起看:
ss -lntcat /proc/sys/net/core/somaxconncat /proc/sys/net/ipv4/tcp_max_syn_backlog它们能帮助你判断 backlog 上限和实际监听能力是否匹配。
CLOSE-WAIT 持续堆积
前面四条主线的排查方法,在物理机和虚拟机上都能直接用。但如果你的服务跑在容器里,尤其是 Kubernetes 集群中,有一些额外的坑必须单独讲。
因为容器环境引入了一层资源隔离:cgroup。它会让你在宿主机上看到的数据和容器内进程实际感受到的压力产生偏差。
前面内存章节已经提到过 cgroup 限额的问题,这里展开讲。
在 K8s 中,Pod 的内存限制通过 resources.limits.memory 设置,最终映射到 cgroup 的 memory.max。关键问题在于:宿主机 free -h 显示的可用内存,和容器能用的内存完全是两个数字。
# 在容器内查看 cgroup 内存限制和当前用量cat /sys/fs/cgroup/memory.maxcat /sys/fs/cgroup/memory.currentcat /sys/fs/cgroup/memory.peakcat /sys/fs/cgroup/memory.eventscat /sys/fs/cgroup/memory.stat如果你在宿主机上排查,需要找到对应容器的 cgroup 路径:
# 通过容器 ID 找到 cgroup 路径crictl inspect <container-id> | grep cgroupPath# 或者直接看 /sys/fs/cgroup/ 下的 kubepods 层级ls /sys/fs/cgroup/kubepods.slice/最值得关注的信号:
memory.events 中的 oom_kill 计数在增长——说明容器已经在被杀了memory.current 接近 memory.max——说明即将触发 OOM 或强回收memory.stat 中的 pgfault、pgmajfault 异常高——说明内存压力已经传导到页面错误K8s 中 CPU 限制通过 cpu.max 实现(cgroup v2),格式是 quota period,比如 200000 100000 表示每 100ms 周期内最多用 200ms CPU 时间(即 2 核)。
问题在于:当容器被限流(throttled)时,top 里看到的 CPU 使用率可能并不高,但进程实际上在被内核强制暂停。这种"看起来不忙但响应很慢"的现象非常容易误判。
# 查看 CPU 限流情况cat /sys/fs/cgroup/cpu.stat输出中最关键的两个字段:
nr_throttled:被限流的次数throttled_usec:累计被限流的微秒数如果 nr_throttled 在持续增长,说明容器的 CPU limit 设得太低,或者业务突发负载超过了配额。这时候不是"CPU 不够",而是"配额不够"。
容器内的排查解决的是"这个进程怎么了",但 K8s 层面还有一些问题是容器内看不到的:
# Pod 是否被驱逐过kubectl get events --field-selector reason=Evicted -n <namespace># Pod 的资源使用和限制kubectl top pod <pod-name> -n <namespace># 节点级别的资源压力kubectl describe node <node-name> | grep -A 10 Conditions# 查看节点是否有 MemoryPressure、DiskPressure、PIDPressurekubectl get nodes -o custom-columns=NAME:.metadata.name,MEM_PRESSURE:.status.conditions[?(@.type==\\"MemoryPressure\\")].status,DISK_PRESSURE:.status.conditions[?(@.type==\\"DiskPressure\\")].statusK8s 节点的 Conditions 里如果出现 MemoryPressure=True 或 DiskPressure=True,说明节点级别的资源已经紧张,kubelet 可能会开始驱逐 Pod。这时候单看某个容器内部是不够的,需要从节点维度理解全局。
前面几节已经把 CPU、内存、磁盘 IO、网络各自的排查方法拆开讲了,但线上真实故障往往不会只在一个维度里老老实实地表现。更常见的情况是,一个资源先出问题,随后把另外两个甚至三个资源一起拖乱,监控面板上看起来像“全线告警”。这时候如果没有顺序感,就很容易被表象带偏,在错误的方向上越查越深。
我更推荐把线上排查理解成一个逐层收敛的过程:先从系统级快照判断主矛盾,再定位到异常进程,再继续下钻到线程、连接、内存区域或系统调用,最后才把根因落到具体代码路径、配置项或者依赖行为上。真正决定排查效率的,不是命令记得多不多,而是你能不能在多个异常之间找出因果链的起点。
路径一:IO 拖慢 → 负载飙升 → CPU 告警
这是最经典的一类级联。磁盘开始排队以后,大量任务会进入不可中断等待状态,也就是常说的 D 状态。此时 load average 会被迅速拉高,监控系统往往先报的是“CPU 告警”或“机器负载过高”,但实际上 CPU 并没有真的忙到算不过来,它只是被 IO 等待拖住了。很多人第一眼看到 CPU 告警就去查热点函数,结果花了很久才发现根因其实是磁盘写爆了。
这种场景建议按下面的顺序查:
vmstat 1 5 # 看 b、wa,确认是否存在明显阻塞和 IO 等待iostat -x -y 1 5 # 看 await、aqu-sz、%util,确认块设备是否排队pidstat -d -p ALL 1 5 # 找出是哪个进程在持续读写磁盘cat /proc/<PID>/io # 看 syscr/syscw、read_bytes/write_bytesstrace -f -tt -T -e trace=write,fsync,fdatasync -p <PID> # 确认是在大量写、频繁 fsync,还是其他 IO 调用判断这条路径时,最值得留意的几个信号是:wa 明显高,vmstat 里的 b 远大于 r,iostat 里的 await 和 aqu-sz 同步抬升,定位到的目标进程在 /proc/<PID>/io 里又表现出很高的写入量或写调用次数。如果修复 IO 问题以后,负载和 CPU 告警一起回落,那基本就能确认这是一条“IO 拖慢 -> 负载升高 -> CPU 被误报”的链路。
路径二:内存紧张 → 频繁回收 → CPU 抖动 + IO 飙升
这条路径比前一种更隐蔽,因为它会让 CPU、内存、IO 同时看起来都“不太对”。当物理内存不足时,内核会频繁触发页面回收,轻一点是 kswapd 在后台忙,重一点就会进入 direct reclaim,直接让前台请求线程参与回收。回收本身要消耗 CPU,如果还伴随脏页写回,就会连带把磁盘 IO 一起抬高,所以最后在监控上看到的往往是三条曲线一起异常。
这种场景建议先确认是不是“内存压力传导成了系统抖动”:
free -h # 先看 available,而不是只看 freevmstat 1 5 # 看 si/so、wa、cs,判断是否开始换页和系统抖动cat /proc/pressure/memory # 看 memory some/full,确认任务是否因内存压力被卡住sar -B 1 5 # 看 pgscank/s、pgscand/s、pgsteal/scat /proc/meminfo | egrep 'MemAvailable|Dirty|Writeback|Slab|SUnreclaim' # 看可用内存、脏页和内核对象dmesg -T | egrep 'oom|page allocation failure|compaction' | tail -n 20 # 查内核是否已经出现明显内存压力迹象# 如果怀疑某个进程在持续吃内存,可以继续下钻pidstat -r -p <PID> 1 5 # 看目标进程的 RSS、缺页和内存变化趋势cat /proc/<PID>/status | egrep 'VmRSS|VmSwap|RssAnon|RssFile' # 分清匿名内存、文件映射和 swappmap -x <PID> | tail -n 20 # 看进程地址空间和内存映射结构如果你看到 MemAvailable 持续走低,memory some/full 抬升,sar -B 里的 pgscand/s 开始出现,甚至内核日志里已经出现 page allocation failure 或压缩回收相关信息,就说明这不是简单的“内存占用大”,而是已经进入了对业务延迟有明显影响的回收阶段。
路径三:网络阻塞 → 线程池耗尽 → CPU 看起来很闲但服务不响应
这类问题经常出现在依赖链很长的服务里。数据库、缓存、下游 HTTP 服务或者消息中间件一旦响应变慢,调用方的工作线程就会被阻塞在网络等待上。线程池如果被这些慢请求占满,新来的请求就算到了机器上也拿不到可用线程,于是表现出来就是“服务几乎不响应”,但 CPU 使用率却并不高。很多人看到 CPU 不高就误以为机器没压力,实际上线程资源已经被网络等待吃空了。
这种场景更适合按“连接状态 -> 连接质量 -> 线程状态”来查:
ss -s # 看整体 TCP 状态是否堆积ss -tan state established # 看 ESTABLISHED 连接数是否异常多ss -tan state close-wait # 看是否存在连接未正常关闭ss -ti # 看 rtt、rto、cwnd,确认链路质量和重传风险ss -m # 看 socket 缓冲和队列是否堆积pidstat -t -p <PID> 1 5 # 看工作线程是否大量挂起、CPU 却不高lsof -p <PID> -i # 看目标进程当前持有的网络连接strace -f -tt -T -p <PID> -e trace=network,epoll_wait,poll,select # 看线程是否长期卡在网络等待如果业务是 Java、Go 或 C++ 这类线程模型明显的服务,还应该结合线程栈一起判断。只要你发现 CPU 不高,但大量连接的 rtt 异常、线程长期阻塞在网络收发或事件等待上,同时连接池或线程池已经接近打满,这条“网络阻塞 -> 线程耗尽 -> 服务超时”的级联关系就基本成立了。
当你看到多个维度同时异常时,不要试图同时解决所有问题。正确思路不是“把所有告警都处理一遍”,而是先找出最先失控的那个点。只有起点找对了,后面那些看起来很吓人的异常才会自己串起来。实际排查时,我更建议按下面这个优先级来做。
full 指标最高,它最可能是因果链的起点sar 回溯,哪个指标最先开始异常# 快速对比三个维度的 PSI 压力cat /proc/pressure/cpucat /proc/pressure/memorycat /proc/pressure/io# 用 sar 对比各维度的异常起始时间sar -u -s 14:00:00 -e 15:00:00 # CPUsar -r -s 14:00:00 -e 15:00:00 # 内存sar -d -p -s 14:00:00 -e 15:00:00 # 磁盘sar -n DEV -s 14:00:00 -e 15:00:00 # 网络记住一个原则:级联故障里,最先异常的那个维度,通常才是真正的根因;后面跟着一起告警的那些维度,很多时候只是连锁反应。你不需要一开始就把所有图都解释清楚,只需要先拿出一条最自洽的因果链,然后再用日志、指标和系统调用把它验证扎实。

这一节保留成真正的速查表形式,但和前面的版本不同,下面每条命令都直接写明“这条命令是干什么的”。这样做的好处是,排查时你不用再回忆“我为什么要敲它”,直接看一眼就知道这条命令适合回答什么问题。
date # 记录故障发生和取样时间,方便对齐监控、日志、链路追踪uptime # 快速看 load average,判断机器整体压力是否突然升高top -b -n 1 | head -n 25 # 看任务总量、CPU 分布和当前最忙的进程vmstat 1 5 # 看 r/b、si/so、cs、wa,判断 CPU、内存、IO 是否一起异常iostat -x -y 1 5 # 看 await、aqu-sz、%util,确认磁盘是否在排队ss -s # 汇总 TCP 各状态连接数,判断是否有连接堆积cat /proc/pressure/cpu # 看 CPU 压力是否已经影响任务调度cat /proc/pressure/memory # 看内存压力是否已经阻塞任务执行cat /proc/pressure/io # 看 IO 压力是否已经让任务大量等待ps aux --sort=-%cpu | head -n 10 # 找出当前 CPU 占用最高的进程mpstat -P ALL 1 5 # 看每个 CPU 核的忙碌程度,判断是否单核打满top -H -p <PID> # 在目标进程内部找最耗 CPU 的线程pidstat -t -u -p <PID> 1 5 # 按线程观察目标进程的 CPU 变化趋势perf top -p <PID> # 直接看热点函数,定位时间到底耗在什么代码上strace -f -tt -T -p <PID> -c # 汇总系统调用占比,判断是不是内核态或调用频率异常free -h # 先看 available 和 swap,判断物理内存是否真的吃紧cat /proc/meminfo | egrep 'MemAvailable|Cached|Dirty|Writeback|Slab|Committed_AS|CommitLimit' # 分清可用内存、页缓存、脏页和内核内存pidstat -r -p <PID> 1 5 # 观察目标进程的 RSS、缺页和内存波动趋势cat /proc/<PID>/status | egrep 'VmSize|VmRSS|VmSwap|RssAnon|RssFile|RssShmem' # 看进程 RSS、匿名页、文件页和 swap 分布pmap -x <PID> # 查看进程地址空间和映射段,辅助判断内存来自哪里slabtop -o # 观察内核 slab 对象是否异常膨胀oomctl dump # 在启用 systemd-oomd 时查看最近的 OOM 清理信息iostat -x -y 1 5 # 看磁盘延迟、队列和忙碌度,判断是否排队pidstat -d -p ALL 1 5 # 找出哪个进程在持续读写磁盘cat /proc/<PID>/io # 看目标进程真实的读写字节和调用次数cat /proc/meminfo | egrep 'Dirty|Writeback' # 看脏页和回写量,判断页缓存刷盘压力strace -f -tt -T -e trace=write,fsync,fdatasync -p <PID> # 确认是否在频繁写日志或频繁同步刷盘ss -s # 汇总 TCP 状态分布,先看是否有明显堆积ss -tan state established # 查看当前存活连接数,判断是否连接过多ss -tan state time-wait # 查看 TIME-WAIT 是否异常多,排查短连接模型问题ss -tan state close-wait # 查看 CLOSE-WAIT 是否堆积,排查连接未正确关闭ss -ti # 看 rtt、rto、cwnd,判断链路质量和重传风险ss -m # 看 socket 缓冲、backlog、drop,判断网络队列是否堆积ss -lnt # 看监听端口的 Recv-Q/Send-Q,判断 accept 是否跟不上cat /proc/sys/net/core/somaxconn # 看应用 backlog 的系统上限是多少cat /proc/sys/net/ipv4/tcp_max_syn_backlog # 看半连接队列上限,排查握手积压sar -u # 回看 CPU 历史使用率,确认异常从什么时候开始sar -r # 回看内存和 swap 历史,判断是否持续吃紧sar -d -p # 回看磁盘设备的历史吞吐和延迟变化sar -n DEV # 回看网卡流量历史,判断是否出现流量突增sar -q # 回看负载、运行队列和阻塞队列历史sar -B # 回看页面回收、缺页和内存压力历史sar -u -s 14:00:00 -e 15:00:00 # 截取指定时间段,和报警时间精确对齐cat /sys/fs/cgroup/memory.max # 看容器内存硬上限,确认真正能用多少内存cat /sys/fs/cgroup/memory.current # 看容器当前已用内存cat /sys/fs/cgroup/memory.events # 看 cgroup 是否已经触发 oom、oom_kill 或高压回收cat /sys/fs/cgroup/cpu.stat # 看 nr_throttled、throttled_usec,判断是否被 CPU 限流kubectl top pod <pod-name> -n <namespace> # 看 Pod 实时资源使用量kubectl describe node <node-name> | grep -A 10 Conditions # 看节点是否存在 MemoryPressure、DiskPressure 等状态kubectl get events --field-selector reason=Evicted -n <namespace> # 看 Pod 是否因为节点压力被驱逐只会背命令,真正出故障时还是容易慌,因为你面对的是一组彼此影响的现象,而不是一道标准题。真正有用的能力,是先判断主矛盾在哪,再把系统级异常一层层收敛到进程、线程、连接、系统调用和配置项上。这样做的好处不是“显得专业”,而是能显著缩短从报警到定位根因的时间。
只要你能把 CPU、内存、磁盘 IO、网络这四条线之间的关系理顺,很多看上去混乱的线上故障都会变得可以拆解、可以验证、也可以复盘。一套排查方法真正有价值,不是因为它列出了很多命令,而是因为你照着它走,最后真的能把问题找出来。
top(1):https://man7.org/linux/man-pages/man1/top.1.htmlvmstat(8):https://man7.org/linux/man-pages/man8/vmstat.8.htmlfree(1):https://man7.org/linux/man-pages/man1/free.1.html/proc/loadavg:https://man7.org/linux/man-pages/man5/proc_loadavg.5.html/proc/stat:https://man7.org/linux/man-pages/man5/proc_stat.5.html/proc/meminfo:https://man7.org/linux/man-pages/man5/proc_meminfo.5.html/proc/<pid>/status:https://man7.org/linux/man-pages/man5/proc_pid_status.5.html/proc/<pid>/io:https://man7.org/linux/man-pages/man5/proc_pid_io.5.htmliostat(1):https://man7.org/linux/man-pages/man1/iostat.1.htmlpidstat(1):https://man7.org/linux/man-pages/man1/pidstat.1.htmlss(8):https://man7.org/linux/man-pages/man8/ss.8.htmllisten(2):https://man7.org/linux/man-pages/man2/listen.2.htmltcp(7):https://man7.org/linux/man-pages/man7/tcp.7.htmlsystemd-oomd 官方文档:https://www.freedesktop.org/software/systemd/man/systemd-oomd.html更多ROS、具身智能相关内容,请关注古月居
👉 关注我们,发现更多有深度的自动驾驶/具身智能/GitHub 内容!
🚀 往期内容回顾 👀
🔥 具身智能 | 从 VLA 到 WAM、VAM 与 UAM:机器人基础模型如何从“看见就做”走向“预测世界再行动”🔥 行业杂谈 | 大型语言模型中的情感智能:从认知科学到技术实践的深度探索🔥 读读代码 | ROS2实时性保障与硬件加速技术详解