转载自公众号:敢敢AUTOHUB
0. 简介
线上机器一旦变慢,最怕的不是问题复杂,而是人一慌就开始乱猜。真正有效的排查,不是上来盯着某个进程猛看,而是先拿到全局快照,再把异常收敛到 CPU、内存、磁盘 IO 或网络中的一条主线,最后定位到具体进程、线程、系统调用或连接状态。这篇指南讲的不是零散命令,而是一套真正能落地的排查顺序——从 PSI 压力指标到容器环境,从 sar 历史回溯到级联故障识别,覆盖现代 Linux 运维排查的完整链路。
监控上显示,接口延迟从 50ms 突然涨到 3s,错误率开始抬头,机器 CPU 告警。你连上服务器,终端光标一闪一闪,脑子里却不一定立刻有答案。很多人这时候会直接猜:是不是流量暴涨了,是不是数据库慢了,是不是有人把日志开成了 DEBUG,是不是内存泄漏了。
猜测当然有时会猜中,但大部分线上排查真正浪费时间的地方,恰恰就是“太早下结论”。
服务器性能问题,表面上看都是“变慢了”,但底层成因并不一样。CPU 打满会变慢,内存回收压力大也会变慢,磁盘排队会变慢,网络链路抖动还是会变慢。更麻烦的是,这几类问题在监控上经常会互相伪装。比如磁盘 IO 排队,会把 load average 拉高;内存紧张导致频繁回收,也可能表现成 CPU 抖动;网络发送缓冲堆积,又可能看起来像应用线程卡住。
所以,线上排查最重要的不是“我会多少命令”,而是“我能不能先判断主矛盾在哪一层”。
1. 先建立全局视角,而不是上来就猜
线上机器出问题以后,第一反应应该是“先拍快照”,而不是“先判断根因”。
这里的所谓全局视角,核心就是两件事:
- 1. 先看机器现在整体处在什么状态。2. 再判断 CPU、内存、磁盘 IO、网络,哪一条线最先失控。
如果这一步做对了,后面的排查会越来越窄;如果这一步做错了,后面的努力大概率是在错误方向上深挖。
1.1 为什么第一眼不能只看 top
很多人收到报警后第一条命令就是 top,这没错,但如果只看 top,信息是不够的。
top 能回答“当前谁最忙”,却不一定能回答“为什么忙”。一个进程 CPU 很高,可能真的是业务计算吃满;也可能只是别的资源拖住了系统,导致这个进程看起来异常活跃。更关键的是,load average 并不等于 CPU 使用率。Linux 会把可运行任务和不可中断等待任务一起计入负载,所以磁盘 IO 排队同样会把负载顶上去。
这就是为什么第一组命令最好不要只有一个 top,而应该是下面这套:
date
uptime
top -b -n 1 | head -n 25
vmstat 1 5
iostat -x -y 1 5
ss -s
这组命令的价值,不在于“全面”,而在于它们互相补位。
date
-
- 用来记录故障时刻,后面要对日志、监控、链路追踪时很有用。
uptime
-
- 快速看负载是否突然抬高。
top
-
- 看进程层面的忙碌情况。
vmstat
-
- 看 CPU、内存、换页、块 IO 是否同步异常。
iostat
-
- 判断磁盘是否真的在排队。
ss -s
- 看连接状态有没有明显堆积。
1.2 拿到快照以后,先问自己四个问题.
load average 高,是 CPU 真不够,还是有大量任务在等 IO?
MemAvailable 是不是真的在掉,还是只是页缓存变大了?
3. 磁盘到底是忙,还是已经排队到影响前台请求?
4. 网络到底是连接数太多,还是某些连接的 RTT、重传、队列出了问题?
只要把这四个问题依次问完,方向通常就不会偏。
1.3 全局判断时,最先看的几个信号
| 指标 | 优先级 | 它回答什么 |
|---|---|---|
load average |
高 | 机器整体压力大不大 |
%Cpu 中的 us/sy/wa/id |
高 | 压力更像来自计算、内核还是等待 |
vmstat 里的 r/b/si/so |
高 | 任务是在抢 CPU、等 IO,还是开始换页 |
iostat 里的 await/aqu-sz/%util |
高 | 磁盘是否已经明显排队 |
ss -s |
高 | TCP 状态是否堆积 |
其中最容易误判的是两个地方。
第一,load average 高,不等于 CPU 满。
第二,wa 高,也不能单独下结论说“磁盘有问题”,因为 iowait 本身并不是一个足够可靠的单指标,必须配合 iostat、vmstat 和进程级 IO 再判断。
1.4 一分钟内怎么做初判
| 现象 | 更像什么 | 先去哪条线 |
|---|---|---|
us+sy 高,id 很低,r 也高 |
计算或系统调用压力 | CPU |
MemAvailable 持续走低,si/so 不为 0 |
内存紧张甚至开始换页 | 内存 |
wa 高,b 高,await 高,aqu-sz 高 |
磁盘 IO 排队 | 磁盘 IO |
TIME-WAIT、CLOSE-WAIT、SYN-RECV 明显异常 |
连接模型或链路问题 | 网络 |
1.5 用 PSI 做更精确的压力判断
上面的初判方法已经够用了,但如果你的内核版本在 4.20 以上,还有一个更直接的工具可以用:PSI(Pressure Stall Information)。
PSI 的核心价值在于:它不是告诉你"某个资源的使用率是多少",而是直接告诉你"有多少比例的时间,任务因为等待这个资源而被卡住了"。这比传统指标更接近业务体感。
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
每个文件会输出类似这样的内容:
some avg10=4.50 avg60=2.80 avg300=1.20 total=18923456
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
这里面最关键的两个概念:
some:至少有一个任务因为等待该资源而被阻塞的时间占比
full:所有任务都在等待该资源、没有任何有效工作在进行的时间占比
判断标准可以这样理解:
| 指标 | 含义 | 什么时候该紧张 |
|---|---|---|
cpu some avg10 > 20 |
最近 10 秒内有明显的 CPU 争抢 | 持续高于 25 就值得关注 |
memory some avg10 > 10 |
内存回收压力已经开始影响任务 | 持续高于 20 要警惕 |
memory full avg10 > 5 |
所有任务都在等内存回收 | 这已经是严重状态 |
io some avg10 > 15 |
IO 等待开始拖慢部分任务 | 持续高于 30 需要立刻排查 |
io full avg10 > 10 |
所有任务都卡在 IO 上 | 磁盘大概率已经成为瓶颈 |
PSI 的好处是它把"资源紧张程度"量化成了一个统一的百分比,不需要你自己去交叉对比多个指标。在全局初判阶段,先看一眼 PSI,往往能比传统方法更快地锁定主矛盾方向。
需要注意的是,PSI 在某些老内核或精简内核上可能没有开启。如果 /proc/pressure/ 目录不存在,说明内核编译时没有打开 CONFIG_PSI,这时就只能回到传统方法。
1.6 用 sar 回溯故障时段的历史数据
线上排查有一个非常现实的困境:你赶到现场时,故障可能已经过去了。
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 在排查中最常用的几个场景:
- 1. 故障已经恢复,但你需要确认故障时段到底发生了什么。
- 2. 需要对比"正常时段"和"异常时段"的指标差异。
- 3. 需要判断某个指标是"突然飙升"还是"缓慢爬坡"——这对根因判断非常关键。
- 4. 需要向团队或管理层提供故障时间线的数据支撑。
一个实用技巧:如果你发现机器上 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)
2. CPU 排查:先判断它是不是“真 CPU 问题”
很多机器报“CPU 告警”时,真正的问题未必是 CPU。
这是线上排查里非常常见的误区。因为在监控系统里,CPU 和负载通常是最早出现在大盘上的,所以大家很容易本能地朝 CPU 方向钻。但 Linux 的负载统计不是“CPU 百分比”那么简单,大量等待磁盘 IO 的任务也会进入负载。所以你看到 load average 很高时,第一反应不应该是“扩容 CPU”,而应该是“先分清这些任务到底在跑,还是在等”。
2.1 CPU 这一段,先看四个点
top -b -n 1 | head -n 20
vmstat 1 5
mpstat -P ALL 1 5
ps aux --sort=-%cpu | head -n 10
这四条命令分别在回答不同的问题。
top 看总量和热点进程。
•vmstat 看可运行队列 r、阻塞任务 b、上下文切换 cs。
• mpstat -P ALL 看是不是只有少数几个核被打满。
• ps 看哪些进程最吃 CPU。
2.2 us、sy、wa 分别意味着什么
很多人会背这几个缩写,但真到排查现场,不一定知道该怎么用它们判断。
us 高,说明 CPU 时间主要花在用户态,常见于业务计算、循环、序列化、压缩、正则、GC。
sy 高,说明内核态开销偏大,常见于系统调用密集、网络包处理开销大、锁竞争严重、频繁 fork。
wa 高,说明 CPU 有相当一部分时间在等 IO 完成,但这时不能只看 CPU 面板,必须继续结合磁盘维度看。
把这三个指标分清楚,排查速度会快很多。因为它们决定了你下一步要不要继续看 CPU,还是应该转向 IO 或网络。
2.3 什么时候才算“真 CPU 问题”
一般来说,下面这组信号同时出现,才更像是 CPU 本身成了主瓶颈:
us+sy 很高,id 很低
• vmstat 的 r 长时间明显大于核数
• mpstat -P ALL 显示多个核持续繁忙
• 没有明显的高 wa、高 b、高 si/so
如果这些条件不满足,就不要急着把锅扣到 CPU 头上。
2.4 一旦确认是 CPU 方向,顺序怎么走
ps aux --sort=-%cpu | head -n 10
top -H -p <PID>
pidstat -t -u -p <PID> 1 5
ps -eLo pid,ppid,tid,psr,pcpu,stat,comm --sort=-pcpu | head
perf top -p <PID>
strace -f -tt -T -p <PID> -c
这个顺序不要乱。
先找进程,再找线程,再看函数热点,最后才去看系统调用统计。因为很多时候,问题在 perf top 那一步就已经收敛了,没必要一上来就进入最重的观察手段。
2.5 三种最常见的 CPU 高场景
2.5.1 us 高,说明算力真的被业务吃掉了
这种场景最典型。比如某段代码里有死循环,某个正则表达式触发了灾难性回溯,某个请求路径引入了巨大的 JSON 序列化开销,或者 Java 程序在高压下频繁 Full GC。它们的共同点是:CPU 时间主要耗在用户态,热点通常集中在少数业务线程上。
这时候最有用的命令是:
top -H -p <PID>
perf top -p <PID>
top -H 解决的是“哪个线程最热”,perf top 解决的是“热在哪里”。前者帮你缩小对象,后者帮你收敛到函数。
2.5.2 sy 高,说明开销大头在内核态
这类问题比单纯的业务计算更隐蔽。
如果你看到 sy 特别高,通常应该怀疑:
• 系统调用过于频繁
• 网络小包风暴
• 日志写得非常碎
• futex 很多,说明锁竞争明显
• 频繁 open / close / stat
这时用 strace -c 很有效:
strace -f -tt -T -p <PID> -c
如果你看到 write、recvfrom、sendto、futex、epoll_wait 这些调用占比异常高,就不要再停留在“CPU 高”这个表象上了,而要追问:为什么这些系统调用会这么密集。
2.5.3 负载高,但 CPU 没满
这是最容易让人误判的一种情况。
表面看机器“负载爆了”,实际上真正忙的不是 CPU,而是别的资源。常见信号是:
load average 很高•
us+sy 没那么夸张•
wa 较高,或者 vmstat 里的 b 很高
这时候如果你继续在 CPU 上深挖,大概率只会越查越偏。正确动作是立刻转到磁盘 IO 或锁等待链路。
2.6 CPU 方向最常见的四个误判
• 把 load average 当成纯 CPU 指标。
• 看到某个进程 %CPU 高,就不再往线程层细分。
• 只看总 CPU,不看 mpstat -P ALL,从而错过单核瓶颈。
• 在虚拟机里忽略 st,误把宿主机争抢当成业务退化。
3. 内存排查:分清是泄漏、缓存,还是回收压力
内存问题看起来比 CPU 更直观,因为“内存快满了”这句话非常容易理解。但真到了 Linux 上,反而更容易因为“看起来很满”而误判。
原因很简单:Linux 会主动用空闲内存做缓存。也就是说,“内存被用掉了”本身不是问题,关键要看这部分内存到底是什么。
3.1 第一眼不要盯着 free,先看 available
free -h
cat /proc/meminfo | egrep 'MemTotal|MemFree|MemAvailable|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab|SReclaimable|SUnreclaim|Committed_AS|CommitLimit'
vmstat 1 5
ps aux --sort=-%mem | head -n 10
很多人第一次看 free -h,会本能地关注 free 那一列,但线上排查时更有判断价值的是 available。
因为 available 更接近“在不明显触发交换的情况下,还能给新进程用多少”。如果 free 很低,但 available 还比较健康,同时 Cached 很大,那大概率只是页缓存正常占用了内存,并不是系统真的快撑不住了。
3.2 内存问题先分成四类看
| 现象 | 更像什么 | 核心判断 |
|---|---|---|
MemAvailable 低,si/so 持续非零 |
物理内存紧张 | 已经开始换页,性能通常会明显抖动 |
VmRSS、RssAnon 持续上涨 |
用户态内存泄漏或对象堆积 | 看趋势,不看某个瞬时值 |
Cached 很大,但 available 还可以 |
正常页缓存 | 不要误判为泄漏 |
Slab、SUnreclaim 异常上涨 |
内核对象膨胀 | 可能是 dentry、inode、socket 相关 |
3.3 为什么内存问题要特别强调“趋势”
因为单次截图很容易骗人。
某个进程此刻 VmRSS 很大,不代表它就有泄漏。可能只是它刚完成一轮批量处理,内存还没回落;也可能是它持有大量文件映射;还可能只是运行期缓存比较多。真正值得警惕的,是“业务高峰过去了,它的 RSS 还是持续往上走,而且回不来”。
所以内存问题的判断,最好带一点时间维度:
pidstat -r -p <PID> 1 5
cat /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
3.4 用户态泄漏通常长什么样
它往往不是“瞬间爆掉”,而是慢慢涨上去。
特征通常有这些:
VmRSS 或 RssAnon 持续增长
• 业务流量已经降下来,内存却不回落
• MemAvailable 越来越低
• 如果已经开始换页,si/so 会变得明显
对 C/C++ 程序来说,这类问题经常和对象生命周期、容器无限增长、缓存淘汰策略失效、跨线程对象持有有关。
对 Java、Go 之类运行时语言来说,还要结合 GC 行为、堆上对象增长、逃逸、引用链去判断。
3.5 页缓存大,不是坏事
这是 Linux 新手最容易被误导的地方。
如果你看到:
Cached 很大
• MemAvailable 仍然不低
• 业务没有明显换页
• 进程 RSS 也不夸张
那这通常不是问题,而是内核在正常利用内存做文件缓存。缓存本来就是为了提升 IO 命中率存在的,只有当它挤压到真正可用内存,或者引发回收压力时,才值得担心。
3.6 内核内存异常,怎么识别
有时候用户态进程看起来都不算夸张,但系统可用内存仍在明显下降。这时就要怀疑是不是内核对象在涨。
可以先看:
slabtop -o
如果 Slab、SUnreclaim 明显上涨,slabtop 里某类对象数量持续膨胀,那问题就未必在业务堆内存本身,而可能在 dentry、inode、socket buffer 之类的内核缓存。
3.7 容器环境里,宿主机“有内存”不代表容器能活
这是现代线上环境里非常值得补的一层。
如果你的服务跑在 cgroup v2 下,容器本身的 memory.max、memory.high、memory.current 比宿主机总内存更关键。很多时候宿主机还没 OOM,但容器因为超过自己的上限已经被强回收甚至被杀了。
cat /sys/fs/cgroup/<cg>/memory.current
cat /sys/fs/cgroup/<cg>/memory.peak
cat /sys/fs/cgroup/<cg>/memory.events
cat /sys/fs/cgroup/<cg>/memory.stat
如果 memory.events 里的 oom、oom_kill 在增长,那问题已经非常明确了。
3.8 现代系统里,别漏掉 systemd-oomd
不少人只会去 dmesg 里找 OOM 日志,但如果机器启用了 systemd-oomd,进程可能在传统内核 OOM 之前就被主动清理掉。
oomctl dump
dmesg -T | grep -Ei 'oom|killed process' | tail -n 20
这一步的价值在于:弄清楚“到底是谁杀了它”。如果这一点都没分清,后面的分析很容易走偏。
3.9 内存方向最常见的四个误判
• 看到 MemFree 很低,就说机器内存不够。
• 看到 Cached 很大,就下结论说“内存泄漏”。
• 只看宿主机总内存,不看 cgroup 限额。
• 用单次 RSS 截图判断泄漏,而不看趋势。
4. 磁盘 IO 排查:先确认是不是排队,再找谁在写
磁盘 IO 问题是最容易“伪装成 CPU 问题”的。
因为一旦块设备开始排队,任务就会进入不可中断等待,负载会上升,CPU 面板上也会出现明显的 wa。如果这时候只盯着 CPU 面板,很容易误以为“CPU 告警就是 CPU 不够”,实际上 CPU 只是在等磁盘。
4.1 这一段第一组命令最好是
vmstat 1 5
iostat -x -y 1 5
pidstat -d -p ALL 1 5
cat /proc/meminfo | egrep 'Dirty|Writeback'
这四个命令配在一起,基本能把 IO 的大方向看清楚。
• vmstat 里的 b 能看到有多少任务在阻塞。
• iostat -x 能看到磁盘是不是在排队。
• pidstat -d 用来找具体哪个进程在读写。
• Dirty 和 Writeback 可以帮助判断页缓存回写压力。
4.2 iostat -x 里最值得看的,不是带宽,而是延迟和队列
很多人一看到 rkB/s、wkB/s 就开始紧张,觉得带宽很大就一定是问题。其实不是。
真正决定“业务为什么慢”的,通常不是吞吐数字本身,而是请求有没有在设备前排队。
判断这件事最有价值的三列是:
• await:一次 IO 从入队到完成的平均时间
• aqu-sz:平均队列长度
• %util:设备忙碌时间占比
这里尤其要强调两个判断点。
第一,await 高而且 aqu-sz 高,才说明“慢是因为排队”。
第二,%util 很高并不永远等于“磁盘一定到极限了”,特别是在现代 SSD、RAID 或更复杂的存储栈上,不能机械地只拿这一列做结论。
4.3 进程级 IO 定位,为什么 /proc/<PID>/io 很关键
系统级看到“磁盘忙”以后,下一步一定要尽快把问题缩到进程层面。
pidstat -d -p ALL 1 5
cat /proc/<PID>/io
pidstat -d 可以快速找出哪个进程 kB_wr/s 或 kB_rd/s 异常高。
而 /proc/<PID>/io 更进一步,能帮你看清楚:
- • 它发起了多少写系统调用• 它真正写到了多少字节• 有没有大量写调用其实只是碎小日志
如果你发现 syscw 特别高,但 write_bytes 并没有高到相同比例,常常意味着写得很碎,调用频率远高于有效写入量。这种场景在日志系统上非常常见。
4.4 fsync 为什么经常是关键嫌疑人
很多程序员以为“写文件慢”就是写得太多,其实线上更常见的情况是:写本身不算多,但每次写完都在同步刷盘。
这时可以用:
strace -f -tt -T -e trace=write,fsync,fdatasync -p <PID>
如果你在输出里反复看到 fsync() 或 fdatasync(),那延迟高的原因就不只是“有写”,而是“每次写都在等介质确认”。数据库、日志、消息持久化模块里,这类问题尤其常见。
4.5 页缓存回写压力,也会把前台请求拖慢
Linux 下大量写操作通常会先进页缓存,再由内核异步刷盘。这种设计本来是为了吞吐,但在写峰值特别高的时候,也可能积压出大量待回写脏页,最终反过来压到前台线程。
所以如果你看到:
Dirty 很高•
Writeback 持续不低•
await 也逐步抬高
那就要考虑是不是页缓存和后台回写开始互相牵制了。
4.6 磁盘 IO 方向最常见的三个根因
- • 生产环境日志级别过低,每个请求都写大量 DEBUG 日志• 数据库或存储服务发生大量随机写或刷盘• 应用写得很碎,还频繁做同步持久化
4.7 IO 方向最常见的三个误判
• 看到 %util=100% 就直接断言“磁盘一定满了”。
• 只看带宽,不看 await 和 aqu-sz。
• 只停留在系统级,不继续追到具体进程和具体调用。
5. 网络排查:不要只数连接,还要看连接质量
网络问题看起来最杂,因为它既可能是链路问题,也可能是应用本身的连接管理问题,还可能是监听队列、socket 缓冲、DNS、对端处理能力共同作用的结果。
所以网络排查不能只看“连接多不多”,而要同时看三件事:
- 1. 连接状态是否异常堆积。2. 单条连接的 RTT、重传、拥塞状态是否异常。3. 监听端口的接收能力和队列是否成了瓶颈。
5.1 第一组命令
ss -s
ss -tan
ss -tan state established
ss -tan state time-wait
ss -tan state close-wait
ss -ti
ss -m
ss -lnt
5.2 TIME-WAIT 多,不一定就是故障
这是网络排查里最经典的误判之一。
很多短连接服务天生就会产生大量 TIME-WAIT,这本身是 TCP 正常关闭流程的一部分。只有当它已经影响到端口资源、建连能力,或者和业务设计明显不匹配时,它才真正构成问题。
相比之下,CLOSE-WAIT 持续增长往往更值得警惕。因为它通常意味着:对端已经把连接关了,而本端没有及时 close()。这更像代码层面的连接清理问题。
5.3 为什么 ss -i 特别重要
很多人网络排查停在 ss -s 或 netstat 这一层,只能看到连接数量,却看不到连接质量。
ss -i 则能直接给出一些真正有判断价值的 TCP 内部信息,比如:
rtt:往返时延•
rto:重传超时•
cwnd:拥塞窗口•
ssthresh:慢启动阈值
如果一个接口超时,但你用 ss -i 看到连接的 rtt、rto 都明显升高,那就说明问题已经不是“连接有没有建立起来”这么简单,而是链路质量或对端处理能力已经出了明显问题。
5.4 ss -m 用来判断 socket 层是不是堆积了
如果连接数量看起来不夸张,但服务仍然发不动、收不动,就应该去看 socket 内存和队列。
ss -m 里值得关注的包括:
rmem_alloc
wmem_alloc
wmem_queued
back_log
sock_drop
这些信息的意义在于:它能帮助你判断,问题是出在链路上,还是应用根本没来得及消费和处理。
5.5 监听端口的问题,别只看“有没有在 LISTEN”
一个端口在 LISTEN,不代表它就处理得过来。
如果服务端 accept() 太慢,或者用户态工作线程已经堆满,监听队列同样会积压。外部表现往往是:
- • 建连慢• 偶发连接失败• 高峰期抖动特别明显
这时要一起看:
ss -lnt
cat /proc/sys/net/core/somaxconn
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
它们能帮助你判断 backlog 上限和实际监听能力是否匹配。
5.6 网络方向最常见的几个根因
-
- • 短连接过多,连接复用不足• 代码没有正确关闭连接,
CLOSE-WAIT
- 持续堆积• RTT、重传升高,链路本身在抖• 服务端消费不过来,socket 队列和监听队列积压• DNS 解析慢或依赖方响应慢,被表面现象误认为“网络慢”
6. 容器与 K8s 环境下的排查补充
前面四条主线的排查方法,在物理机和虚拟机上都能直接用。但如果你的服务跑在容器里,尤其是 Kubernetes 集群中,有一些额外的坑必须单独讲。
因为容器环境引入了一层资源隔离:cgroup。它会让你在宿主机上看到的数据和容器内进程实际感受到的压力产生偏差。
6.1 容器里的"内存够用"和"真的够用"是两回事
前面内存章节已经提到过 cgroup 限额的问题,这里展开讲。
在 K8s 中,Pod 的内存限制通过 resources.limits.memory 设置,最终映射到 cgroup 的 memory.max。关键问题在于:宿主机 free -h 显示的可用内存,和容器能用的内存完全是两个数字。
# 在容器内查看 cgroup 内存限制和当前用量
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.peak
cat /sys/fs/cgroup/memory.events
cat /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 异常高——说明内存压力已经传导到页面错误
6.2 容器的 CPU 限流比 CPU 满更隐蔽
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 不够",而是"配额不够"。
6.3 K8s 层面还要看什么
容器内的排查解决的是"这个进程怎么了",但 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、PIDPressure
kubectl get nodes -o custom-columns=NAME:.metadata.name,MEM_PRESSURE:.status.conditions[?(@.type==\"MemoryPressure\")].status,DISK_PRESSURE:.status.conditions[?(@.type==\"DiskPressure\")].status
K8s 节点的 Conditions 里如果出现 MemoryPressure=True 或 DiskPressure=True,说明节点级别的资源已经紧张,kubelet 可能会开始驱逐 Pod。这时候单看某个容器内部是不够的,需要从节点维度理解全局。
6.4 容器环境排查最常见的三个误判
- • 在宿主机上看内存充足,就认为容器不会 OOM。
- • 看到容器 CPU 使用率不高,就排除 CPU 问题——实际上可能是被限流了。
- • 只在容器内排查,不看 K8s 事件和节点状态,错过驱逐和调度层面的问题。
7. 把四条线串起来:真正的排查顺序
前面几节已经把 CPU、内存、磁盘 IO、网络各自的排查方法拆开讲了,但线上真实故障往往不会只在一个维度里老老实实地表现。更常见的情况是,一个资源先出问题,随后把另外两个甚至三个资源一起拖乱,监控面板上看起来像“全线告警”。这时候如果没有顺序感,就很容易被表象带偏,在错误的方向上越查越深。
我更推荐把线上排查理解成一个逐层收敛的过程:先从系统级快照判断主矛盾,再定位到异常进程,再继续下钻到线程、连接、内存区域或系统调用,最后才把根因落到具体代码路径、配置项或者依赖行为上。真正决定排查效率的,不是命令记得多不多,而是你能不能在多个异常之间找出因果链的起点。
7.1 最常见的三条级联路径
路径一: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_bytes
strace -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,而不是只看 free
vmstat 1 5 # 看 si/so、wa、cs,判断是否开始换页和系统抖动
cat /proc/pressure/memory # 看 memory some/full,确认任务是否因内存压力被卡住
sar -B 1 5 # 看 pgscank/s、pgscand/s、pgsteal/s
cat /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' # 分清匿名内存、文件映射和 swap
pmap -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 异常、线程长期阻塞在网络收发或事件等待上,同时连接池或线程池已经接近打满,这条“网络阻塞 -> 线程耗尽 -> 服务超时”的级联关系就基本成立了。
7.2 级联故障的通用判断策略
当你看到多个维度同时异常时,不要试图同时解决所有问题。正确思路不是“把所有告警都处理一遍”,而是先找出最先失控的那个点。只有起点找对了,后面那些看起来很吓人的异常才会自己串起来。实际排查时,我更建议按下面这个优先级来做。
1. 先看 PSI:哪个资源的 full 指标最高,它最可能是因果链的起点
2. 再看时间线:用 sar 回溯,哪个指标最先开始异常3. 最后做假设验证:如果 X 是根因,那 Y 和 Z 的异常能不能被解释
# 快速对比三个维度的 PSI 压力
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
# 用 sar 对比各维度的异常起始时间
sar -u -s 14:00:00 -e 15:00:00 # CPU
sar -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 # 网络
记住一个原则:级联故障里,最先异常的那个维度,通常才是真正的根因;后面跟着一起告警的那些维度,很多时候只是连锁反应。你不需要一开始就把所有图都解释清楚,只需要先拿出一条最自洽的因果链,然后再用日志、指标和系统调用把它验证扎实。
8. 最常用的一页速查表
这一节保留成真正的速查表形式,但和前面的版本不同,下面每条命令都直接写明“这条命令是干什么的”。这样做的好处是,排查时你不用再回忆“我为什么要敲它”,直接看一眼就知道这条命令适合回答什么问题。
8.1 全局入口
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 压力是否已经让任务大量等待
8.2 CPU
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 # 汇总系统调用占比,判断是不是内核态或调用频率异常
8.3 内存
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 清理信息
8.4 磁盘 IO
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> # 确认是否在频繁写日志或频繁同步刷盘
8.5 网络
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 # 看半连接队列上限,排查握手积压
8.6 sar 历史回溯
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 # 截取指定时间段,和报警时间精确对齐
8.7 容器 / K8s
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 是否因为节点压力被驱逐
9.总结
只会背命令,真正出故障时还是容易慌,因为你面对的是一组彼此影响的现象,而不是一道标准题。真正有用的能力,是先判断主矛盾在哪,再把系统级异常一层层收敛到进程、线程、连接、系统调用和配置项上。这样做的好处不是“显得专业”,而是能显著缩短从报警到定位根因的时间。
只要你能把 CPU、内存、磁盘 IO、网络这四条线之间的关系理顺,很多看上去混乱的线上故障都会变得可以拆解、可以验证、也可以复盘。一套排查方法真正有价值,不是因为它列出了很多命令,而是因为你照着它走,最后真的能把问题找出来。
参考资料
1. top(1):https://man7.org/linux/man-pages/man1/top.1.html
2. vmstat(8):https://man7.org/linux/man-pages/man8/vmstat.8.html
3. free(1):https://man7.org/linux/man-pages/man1/free.1.html
4. /proc/loadavg:https://man7.org/linux/man-pages/man5/proc_loadavg.5.html
5. /proc/stat:https://man7.org/linux/man-pages/man5/proc_stat.5.html
6. /proc/meminfo:https://man7.org/linux/man-pages/man5/proc_meminfo.5.html
7. /proc/<pid>/status:https://man7.org/linux/man-pages/man5/proc_pid_status.5.html
8. /proc/<pid>/io:https://man7.org/linux/man-pages/man5/proc_pid_io.5.html
9. iostat(1):https://man7.org/linux/man-pages/man1/iostat.1.html
10. pidstat(1):https://man7.org/linux/man-pages/man1/pidstat.1.html
11. ss(8):https://man7.org/linux/man-pages/man8/ss.8.html
12. listen(2):https://man7.org/linux/man-pages/man2/listen.2.html
13. tcp(7):https://man7.org/linux/man-pages/man7/tcp.7.html
14. PSI 官方文档:https://docs.kernel.org/accounting/psi.html
15. cgroup v2 官方文档:https://docs.kernel.org/admin-guide/cgroup-v2.html
16. systemd-oomd 官方文档:https://www.freedesktop.org/software/systemd/man/systemd-oomd.html
216