半夜两点,手机震了,值班群里有人贴了一张监控截图:某台业务服务器CPU Load已经冲到100以上,机器跟死了一样。你从床上爬起来,打开终端第一件事是敲什么?很多人下意识就是top,然后盯着CPU百分比发呆——这恰恰是最容易走偏的起点。Linux系统里CPU、内存、硬盘、网络这四类核心故障从来不是独立出现的,内存压力会引发磁盘回写,磁盘回写又拉高Load,网络重传还会让应用层卡顿,单独看任何一个指标都可能把排查方向带错。
这篇文章沉淀的是我这几年处理Linux服务器故障时总结的一套排查套路:针对CPU、内存、硬盘、网络四类故障,每个维度该看哪些指标、哪些命令能一次问到点子上、哪些“常识”其实是坑。适合刚开始维护服务器的开发者,也适合正在值班的新手运维,哪怕你之前只会top和df -h,照着这个顺序走一遍,也能在最短时间内把故障面缩小到一个可处理的范围。
1. 排查前的准备:先定流程再碰命令
1.1 为什么一上来不要急着看top
top给出的是一瞬间的快照,而故障排查的本质是判断趋势。CPU使用率80%,如果是业务高峰期的正常波动,你甚至不需要处理;如果是凌晨三点还在80%,那就大概率有问题。没有历史数据做参照,任何快照都是孤证。
所以我现在的习惯是:无论接到什么告警,先按固定顺序回答四个问题——故障影响的是单台机器还是整个集群?告警指标是持续异常还是偶尔抖动?最近有没有发布、配置变更或定时任务触发?系统能不能稳定复现问题?这四个问题决定你接下来是重启了事,还是要深挖根因。
1.2 十分钟内要完成的五件事
故障响应最怕脑子里一团乱麻,手也乱敲命令。遇到告警,前十分钟我按固定动作来:
- 拉取最近一小时的历史数据,确认异常是从什么时候开始的;
- 记录当前时刻的Load、CPU、内存、磁盘吞吐、网络连接数作为现场基线;
- 用
top和ps的批处理模式抓取进程级快照,保存到文件; - 检查系统日志和内核日志,重点看有没有OOM、I/O Error、软锁之类关键字;
- 如果决定继续观察,再考虑操作,不要一上来就重启进程。
这五步做完,即使后面把机器搞挂了,你手里也还有一份可以回看的数据现场。重启是最后手段,它会抹掉大量线索。
1.3 常用工具的分工与选型
工具不在多,关键是用对地方。我平时常驻的排查工具清单如下,按资源维度分开:
| 工具 | 主要用途 | 重点关注指标 |
|---|---|---|
top/htop | 进程级CPU和内存实时查看 | us/sy/wa/st、RES、Load |
vmstat | CPU、内存、IO整体状态 | r、cs、wa、si/so |
sar | 历史数据回放,时间序列分析 | 各项指标的走势 |
free | 内存总量与可用量 | available、buff/cache |
iostat | 磁盘IO详细统计 | %util、await、rrqm/s |
df/du | 磁盘容量与文件占用 | 空间、inode |
ss/netstat | 网络连接与socket统计 | 连接状态分布、队列 |
ethtool -S | 网卡底层收发统计 | 丢包、错包、重传 |
dmesg | 内核日志 | OOM、I/O error、hung task |
有一点要提醒:不要在一个工具上恋战。top看的是表象,vmstat看的是承压点,iostat和sar才是指向根因的线索。下面四个章节,每个都按“看什么、怎么判、下一步做什么”的顺序展开。
2. CPU异常:负载飙高不等于CPU满负荷
2.1 load average三元组的正确读法
top第一行那个load average: 80.21, 72.34, 55.10,三个数分别代表过去1分钟、5分钟、15分钟的平均活跃进程数。关键就在这里:活跃进程包括正在CPU上运行的进程,也包括处在不可中断睡眠状态(D状态)的进程。
判断Load是否过载,首先要看你的机器有几个逻辑核。用nproc或grep -c processor /proc/cpuinfo查看,Load持续大于核数,说明有进程在排队。但反过来说,Load很高不代表CPU真的忙——如果一堆进程卡在D状态等磁盘或网络,CPU可能闲得很,Load照样飙。所以Load高的时候,下一步看的一定是CPU时间细分。
2.2 us、sy、wa、st各代表什么
top里那行%Cpu(s): us, sy, ni, id, wa, hi, si, st,我平时只看五个:
- us:用户态CPU占比,应用代码在跑;
- sy:系统态CPU占比,内核在做系统调用、中断处理;
- wa:等待I/O完成的时间,这个高说明CPU在干等磁盘或网络;
- st:被虚拟机管理程序偷走的时间,云主机上常见说明邻居在抢资源;
- id:完全空闲。
如果us高,说明应用真的在消耗CPU,下一步用top按P排序找进程;如果sy高,往往和系统调用频繁或中断风暴有关;如果wa高,别在CPU维度上浪费时间,直接去查磁盘;如果st高,那是宿主机层面的问题,你优化镜像都未必有用。
2.3 用vmstat看运行队列和上下文切换
很多人不知道vmstat在CPU维度比top更直接。执行vmstat 1,重点看两个列:
- r(run queue):正在运行和等待CPU的进程数,如果长期大于核数,CPU是真的过载了;
- cs(context switches):每秒上下文切换次数,数值异常飙升说明线程调度过于频繁。
上下文切换高的经典场景是线程数爆炸的应用,比如一个进程起了几百个线程同时疯狂抢锁。你可以用pidstat -t -p <pid> 1进一步看每个线程的CPU占比,再用perf top看热点函数。上下文切换本身不是病,它只是症状,病根往往是锁竞争或线程模型设计有问题。
2.4 最常见的CPU假警报:D状态进程
这是我最想强调的一点。某次线上故障,告警显示CPU Load 120,我兴冲冲去查CPU,结果us只有8%,id还有一大半。再一看vmstat,wa飙到80以上,一堆进程挂在D状态。D状态全程不可中断睡眠,一般是进程在做磁盘I/O或等待网络时被卡住,这时候Load被拉高,但CPU资源其实没被消耗。
快速找D状态进程的命令:
ps -eo state,pid,ppid,comm,wchan | awk '$1=="D"'wchan列会告诉你进程卡在内核的哪个函数上,比如等块设备、等锁。看到大量D状态进程,立刻转向磁盘或网络方向排查,而不是在CPU维度继续钻牛角尖。
另外,僵尸进程(Z状态)不会占CPU,但会占进程表项。如果大量出现,多半是父进程没正确回收子进程,属于代码问题,清理进程没用,得改程序。
3. 内存故障:从OOM到Slab缓存的一整套排查路径
3.1 free输出里的猫腻
free -m的输出,很多人只看total和used,看完就喊“内存爆了”。其实Linux里“已用内存”和“真正不够用”是两回事。
$ free -m total used free shared buff/cache available Mem: 15964 12040 612 82 3312 3618buff/cache是内核用来缓存文件数据的,这部分内存在进程需要时可以回收。真正要盯的是available这一列,它才是“在不触发交换的前提下还能分配给新进程的内存”。如果free很小但available还够,说明只是缓存占得多,机器根本没有内存压力;如果available持续走低,那才是真告急。
判断内存是不是真不够,还有一个刚硬指标是swap的使用情况。free里si/so反映的是内存交换进出的速率,如果vmstat里si和so不为零且持续跳动,说明内存已经吃紧到需要磁盘来兜底了,这时候应用卡顿就不可避免。
3.2 OOM killer实际上是按什么逻辑杀进程的
内存耗尽的最终表现是OOM,直接把进程杀掉。内核日志会留下记录:
dmesg | grep -i -E "killed process|out of memory"能看到类似“Out of memory: Killed process 1234 (java)”的条目。内核杀谁不是随机的,它给每个进程算一个oom_score,分数越高越可能被干掉。查看进程当前得分:
cat /proc/<pid>/oom_score粗略地说,分值和进程占用的内存成正比,同时和oom_adj的调整值有关。某些关键进程如果不想被杀,可以用echo -1000 > /proc/<pid>/oom_adj,但要清楚这只是把锅甩给了别人,真正的解法还是给机器扩容或者修内存泄漏。
插一句,OOM最常见的时间点反而是进程刚启动的瞬间。很多服务启动时要加载大量数据,瞬时内存需求超过限制被内核秒杀,表现就是“一启动就退出”。查这种问题,dmesg里的OOM记录往往被误以为没有,因为日志可能被刷掉了,建议提前配置好日志持久化。
3.3 内存泄漏的定位思路
运维场景里最头疼的是内存缓慢增长,一两天不重启就吃光。这种问题的定位思路分三步:
- 先用
top按RES排序,找到内存持续增长的进程; - 观察一段时间,确认是匀速增长还是阶梯式增长;
- 用
pidstat -r 1记录进程的内存变化率,配合cat /proc/<pid>/smaps查看具体内存映射。
如果是Java或Go类应用,还得结合堆转储分析,这类工具不展开讲。这里说一个通用骚操作:临时触发内存回收,判断是进程真泄漏还是内核缓存悬空:
sync; echo 3 > /proc/sys/vm/drop_caches如果drop_caches之后available大幅回升,说明是页缓存和dentry/inode缓存可以回收再利用,不是进程泄漏;如果available纹丝不动,那问题就在用户态进程里。这条经验帮我排掉过很多“假内存泄漏”的雷。
3.4 Slab缓存:容易被忽视的内核内存
进程内存查完没问题,但内存还是不够,就要看/proc/meminfo里的Slab字段了:
grep -E "Slab|SReclaimable|SUnreclaim" /proc/meminfoSUnreclaim表示不可回收的内核内存,如果这个值持续增长,大概率是内核模块或文件系统缓存泄漏。查明细可以看/proc/slabinfo,最常见的元凶是dentry和inode缓存,在一些文件数量巨大的目录场景下,内核会缓存大量目录项。
临时缓解可以用echo 2 > /proc/sys/vm/drop_caches回收dentry和inode缓存,但根治还是要找到是谁在疯狂创建文件或者是谁的内核模块导致异常。Slab相关的坑比较深,日常运维记住“SUnreclaim持续涨 = 内核层面有问题”这一个判断就够用了。
4. 磁盘故障:既不只看df,也不只看iostat
4.1 比磁盘满更隐蔽的inode耗尽
df -h显示磁盘还有几个G,业务却报“No space left on device”,这种矛盾我见过不止一次。原因大概率是inode耗尽。inode是文件系统用来记录文件元数据的结构,每个文件占一个inode,小文件特别多的情况下,磁盘空间没满,inode先用完了。
排查命令:
df -i看IUsed%那一栏,接近100%就要警惕。典型场景是:日志系统打出了海量小文件、消息队列积压了大量临时文件、某个目录下有几十万个缓存文件。定位到目录后用for i in /data/*; do echo "$i: $(find $i | wc -l)"; done逐层数文件数量,找出元凶目录后清理或调整业务写入策略。
还有一个高频坑:磁盘空间明明“删了文件”,df却不释放。这种情况基本是某个进程仍然持有已删除文件的文件句柄。处理方法是lsof | grep deleted找到占用进程,重启它或让它重开日志文件,空间才会真正释放。很多东西看着快被删光了,其实都躺在进程的句柄里。
4.2 iostat的%util和await不能对着一个数猜
I/O性能诊断用iostat -x 1,输出里的关键列是这样:
- %util:设备忙的时间占比,注意它不等于单次IO速度;
- await:一次I/O从发起到完成的总等待时间,包含排队和设备处理;
- r_await、w_await:区分读和写;
- aqu-sz:平均队长的估计值;
- rrqm/s、wrqm/s:每秒合并的读写请求数。
%util到了100%,只能说明设备很忙,不能直接断定是磁盘坏还是业务IO量太大。要看队列和水位:如果%util 100但await只有几毫秒,说明磁盘在高速处理大量请求,这是业务IO量本就大;如果%util不到80但await已经几百毫秒,说明单个请求被卡住了,往往是磁盘自身性能劣化或者RAID降级。
SSD和机械盘的await标准完全不一样:机械盘await几十毫秒正常,SSD超过20毫秒就要警惕。对比r_await和w_await也能发现规律:读延迟高而写延迟正常,大概率在内存回收或读取缓存命中率低;写延迟高则要怀疑日志盘压力、文件系统日志模式或快照任务。
4.3 内核日志和SMART里的“实锤”
磁盘硬件层面的故障,软件工具再花哨也绕不过两个入口:内核日志和SMART信息。
dmesg | grep -i -E "i/o error|sd[a-z]|ata[0-9]" smartctl -a /dev/sda内核日志里如果出现I/O error或task abort,基本上是硬件已经出问题了。SMART信息重点看Reallocated_Sector_Ct、Current_Pending_Sector和UDMA_CRC_Error_Count,前两个跟盘面坏道直接相关,第三个往往暗示线缆或接口接触不良。注意,现在企业级固态盘的SMART字段各家定义有别,不能只看数值大小,要和出厂基线比。
还有一个每块生产环境硬盘都该警惕的现象:文件系统被内核强制以只读方式重新挂载。表现是mount输出里出现ro,df还能看到盘但所有写操作都报只读错误。这种是内核检测到严重文件系统错误后的保护机制,处理顺序必须是:先停机挂载,再fsck,确认修复后再重新挂载,而不是原地硬改remount。
4.4 磁盘故障是怎么伪装成CPU问题的
回到第2章说的D状态进程。磁盘故障最常见的伪装就是Load飙升、应用卡死、CPU却空闲。因为I/O卡住后,大量进程进入不可中断睡眠,Load升高,但CPU无事可做。排查时记住这条链路:Load高 → 查D状态进程 → 看vmstat的wa列 → 转iostat和dmesg。只要看到wa显著高,CPU方向可以立刻止损,去查磁盘。
5. 网络故障:连接、延迟与丢包的边界判断
5.1 先给连接状态排个序
网络故障的排查起点,我建议不是ping,而是ss。因为绝大多数“网络卡”本质是连接层出了问题,要么是连接堆积,要么是握手超时。
先看汇总:
ss -s这一条直接给出TIME-WAIT、ESTAB、SYN-SENT等各类socket数量。接着看具体连接状态分布:
ss -ant | awk '{print $1}' | sort | uniq -c重点关注三件事:ESTABLISHED数量是否远高于业务应有水平;SYN-RECV堆积说明后端服务没来得及接受新连接;TIME-WAIT大量出现则说明短连接频繁建立关闭。
TIME-WAIT多不等于故障,只要系统层面的连接池配置正常,TIME-WAIT会被内核慢慢回收。真正要拉响警报的是ss -lnt里Recv-Q或Send-Q数值持续不为零,这代表连接队列已经堆满,新连接开始被丢弃。配合netstat -s | grep -i "listen"看监听过载计数,就能确认服务是不是accept太慢。
5.2 服务端TCP重传意味着什么
网络抖动最狡猾的表现是:ping丢包率为零,业务却时不时卡一下。这种情况要去看TCP层的重传统计。
nstat -az | grep -i -E "retrans|listen"如果TcpRetransSegs(累计重传次数)持续快速增加,说明网络上确实存在丢包,只是ICMP测试的探测流量太轻没碰到。重传率太高,端到端的实际吞吐会断崖式下降,因为TCP要反复等待确认。
这时候做两件事:第一,用ip addr和ethtool eth0检查本机网卡有没有速率协商问题,比如千兆网卡掉到百兆模式;第二,用mtr看路径上每一跳的丢包率,判断丢包是发生在机房内部链路还是跨运营商链路上。一次我在实际排障中发现,问题出在中间某个交换机的某一跳,单节点丢包20%,但终端只看到轻微的卡顿——因为TCP的重传机制把大部分丢包都消化了,只有重传统计暴露了真相。
5.3 网卡计数器里的硬件级证据
软件层面看不出问题的时候,要从网卡底层找证据:
ethtool -S eth0 | grep -E "rx_dropped|rx_errors|tx_dropped|tx_errors|rx_missed|rx_fifo"rx_dropped高,常见原因包括:网卡ring buffer溢出(可以用ethtool -G eth0 rx 4096加大)、内核协议栈处理不过来、驱动bug。rx_errors高则要怀疑物理链路质量,比如网线劣化、光模块漂移。
还有一类情况是带宽被占满,表现是应用延迟高但连接状态正常。用sar -n DEV 1看网卡每秒吞吐,如果rxkB/s或txkB/s长期贴着链路速率上限,那就不叫故障,叫容量规划问题。千万别在这个阶段去调TCP参数期望立竿见影,先把流量压下来再说。
5.4 连通性问题的判断顺序
“连不上”和“卡顿”是两种完全不同的网络故障。前者排查顺序是:网卡up没up(ip link)→ 路由通不通(ip route)→ 对端端口开没开(nc -vz)→ 防火墙有没有拦(iptables规则)。后者才是上面说的延迟、重传、队列那些事。把这两条线分清,至少能节省半小时的无效操作。
另外强调一个端口连通性的细节:nc -vz验证的是TCP能不能握手成功,它只能证明TCP层可达,应用层是否正常还要看实际请求。很多服务是“端口开着但业务死锁”,这时候你再怎么测端口都是通的。
6. 复合故障现场:一个真实案例的完整复盘
6.1 现象:Load飙到120,CPU却几乎不忙
为了把上面四个维度串起来,我讲一个典型的复合故障案例,细节已经做了脱敏处理。
某业务系统凌晨2点告警,表现是:Load从平时的3左右瞬间飙到120,页面请求全部超时。值班同学第一反应查了top,发现us不到10%,CPU大量空闲,于是陷入“Load明明满了CPU却没跑满”的困惑。这就是典型的判断卡在表象上。
6.2 顺着D状态追根:从CPU到磁盘
我接手后的第一件事是执行ps -eo state,pid,comm,wchan | awk '$1=="D"',发现超过两百个进程挂在D状态,wchan指向块设备I/O。再看vmstat 1,wa列接近90%,r列也很高但us和sy都很低。到这里,问题已经明确转移到磁盘方向。
紧接着用iostat -x 1看那几块数据盘,发现其中一块盘%util长时间100%,await从正常的5毫秒飙到800毫秒以上,w_await更高。再查dmesg,没有I/O error,但能看到task hung相关警告。基本可以确定:这块磁盘或它的IO路径正在被大量写请求塞满。
6.3 真正的根因:内存脏页引发写风暴
问题还要再问一层:为什么两点钟突然有这么多写入?翻crontab和sar历史数据,发现这个时间窗口正好在跑一个数据归档脚本:把近一个月的日志从数据盘A复制到数据盘B,再对A上的原文件做压缩删除。复制和删除同时读写同一块盘,IO一下就炸了。
但最隐蔽的推手是内存。/proc/meminfo显示SUnreclaim和脏页数量异常高,大量缓存没有及时落盘,内核在静默状态下持续做后台回写。回写和归档任务的IO叠加,才把磁盘彻底压垮。也就是说,表面是磁盘故障,诱因是内存的脏页回收机制,底层触发是定时任务的IO风暴。
6.4 处理方案与复盘改进
紧急处理:暂停归档脚本,等待脏页回写完成,系统在几分钟内恢复。长期方案做了三件事:
- 将归档任务拆成多个小批次,用
ionice降低其IO优先级,错开执行时间; - 调整内核脏页参数,把
vm.dirty_background_ratio从默认值调低,让内核更早启动回写,避免脏页积压后集中爆发; - 给磁盘IO加监控采样,只要await超过阈值就自动告警,避免再次被动等半夜被震醒。
这个案例完整演示了CPU、内存、磁盘、网络四类指标如何互相“感染”:内存压力伪装成磁盘故障,磁盘故障又伪装成CPU Load过载。排查的时候,如果只停在第一层表象,就会在错误的方向上耗掉大量时间。
复盘时最值得记住的一句是:故障现场的第一手数据,永远比事后猜测值钱。每次排障都按“Load→D状态→wa→iostat→dmesg”这条链路走下来,我的判断效率比一开始直接盯着top高很多。另外建议每个维护者都在自己常用的服务器上配置好sar历史数据采集和内核日志持久化,这次能快速定位到凌晨2点的异常,靠的都是提前存好的历史曲线,而不是故障发生时才想起来去抓现场。