☰
Linux服务器四类资源故障排查指南:CPU、内存、磁盘、网络
2026/10/20 8:18:53 网站建设 项目流程

1. 四类资源故障的排查思路,先搭一套方法论

接手一台出问题的Linux服务器,第一件事不是急着一通命令猛敲,而是先想清楚故障到底在哪个层面。我这些年修过不少线上事故,发现很多人卡住,不是因为命令不熟,而是因为脑子里没有一条清晰的定位路径——看到CPU高就去杀进程,看到内存不够就加内存条,这是典型的“头痛医头”,往往治标不治本。

1.1 从现象到根因:一条通用的定位链路

不管哪类故障,排查的底层逻辑都是一样的。我把这条链路总结成四步:

  • 第一步,确认现象。是响应变慢、服务不可用、还是用户反馈报错?现象决定排查方向。
  • 第二步,看指标画像。通过top、free、iostat、sar这类命令,先给机器的CPU、内存、磁盘、网络画一张“体检表”,明确是哪个维度亮红灯。
  • 第三步,锁定肇事进程。指标只是结果,真正干活的还是进程。顺着指标往下找到具体进程或线程,这一步往往是成败关键。
  • 第四步,追根因。进程高CPU是有死循环还是GC太频繁?内存紧张是泄漏还是缓存不合理?结合日志、调用链、代码逻辑做最终判断。

整套链路说起来简单,但实际执行时有一个非常容易踩的坑:开局就扎进具体工具里出不来。比如看到load average飙到50,就对着top反复刷新,抓了一堆不痛不痒的截图,却忘了先看一眼是不是某台机器本身只有2核,或者是不是磁盘IO卡死导致一堆进程处于不可中断的D状态。所以,先画像,再定位,这个顺序千万别反。

1.2 建立指标基线的意识

排查故障最大的倚仗,不是你记了多少命令,而是你手里有没有“这台机器正常时到底长什么样”的参照物。没有基线,你会连“CPU用户态占用30%算不算高”这种基础问题都拿不准,更别说判断异常了。

一个比较务实的做法,是把四类核心指标在平时就记录下来。比如CPU的空闲率、负载系数与核数的比例、内存的available余量、Swap使用率、磁盘util百分比和await时延、网卡出入带宽和重传统计。你可以用监控系统存历史趋势,也可以在博客里记个快照,总之不能是零。等到故障发生时,拿实时数据和基线一比,异常点会自己跳出来。我自己习惯重点关注“异常偏离”而不是“绝对值”,因为不同的硬件规格和业务模型,绝对值差异极大。

2. CPU故障排查:搞清楚是忙、是等、还是在“空转”

CPU这一块,最容易混淆的是“高负载”和“高占用”这两个概念。不少刚入行的同学一看到load average数值很大就断定CPU不够用,急吼吼地加核数,结果发现加了核负载纹丝不动——因为他们根本没搞明白,负载高不一定意味着CPU在拼命算,也可能是在排队等待。

2.1 负载不等于CPU占用率,先学会区分

Load average(系统平均负载)本质上是一个“运行队列长度”的统计值,它既包含正在CPU上运行的线程,也包含等待CPU调度的线程,还包含处于不可中断睡眠状态(D状态)的线程。这就导致一个现象:磁盘卡死了,一堆进程阻塞在IO上,load也能被顶得很高,但此时CPU其实是空闲的。

遇到load高,我的习惯是执行top看一眼第一行的load,再按数字1看每个核的占用分布,同时敲vmstat 1 5看r列(运行队列里的线程数)和b列(阻塞在IO上的线程数)。如果r高、CPU占用也高,那是真忙;如果r不高、b高、CPU的wa列往上走,那多半是磁盘在拖后腿。这一下就分清了矛头该指向谁。

下面这段top输出是我处理某模拟项目X时截的典型症状:

top - 14:32:10 up 120 days, 3 users, load average: 18.31, 15.26, 12.03 Tasks: 360 total, 3 running, 357 sleeping, 0 stopped, 0 zombie %Cpu(s): 80.3 us, 12.1 sy, 0.0 ni, 2.5 id, 0.3 wa, 0.0 hi, 4.8 si, 0.0 st

load平均已经到18,但%us只有80%,%wa只有0.3%,没有D状态进程堆积的迹象,这就是典型的计算密集型负载超高,直接往进程列表里找凶手即可。

2.2 定位高CPU占用的线程,细分用户态和内核态

确认是CPU真忙之后,该上工具了。top里按P键按CPU占用排序,这是最基础的。但top有个局限性——它默认展示的是进程整体,如果你遇到一个多线程服务,一个进程下几百个线程,进程总CPU高但不知道是哪条线程在作妖,就必须往下钻一层:

top -H -p <pid>

这条命令会展开该进程下属的所有线程,按下大写的P排序,就能看到具体哪条线程在疯狂吃CPU。拿到线程号之后,很多场景还需要把线程号换算成十六进制,再用jstack或gdb去抓Java、C++程序的调用栈,才能定位到代码行。十六进制转换可以直接用printf '%x\n' <tid>,别手算。

再进一步,用pidstat按固定间隔采样,能更干净地看到进程或线程的CPU变化曲线:

pidstat -p <pid> -t 1 5

如果CPU消耗主要落在内核态(sy占比高),我会顺手用perf top看看内核里的热点函数。我曾经遇到过一例网络包处理场景,用户态CPU不高、软中断(si)吃满了CPU,perf top里全是softirq相关的符号,最后查出来是网卡多队列配置不合理,中断全挤在一个核上——这类问题你光看进程是永远找不到的。

2.3 上下文切换与中断失衡,隐蔽的CPU杀手

还有一种很容易被忽略的CPU故障:整体占用率看着还行,但系统就是卡顿得厉害。这种情况我建议看一眼vmstat输出里的cs(上下文切换次数)列。每秒几十万次的上下文切换,通常意味着系统里有大量线程在频繁争抢锁或者进行无意义的轮询。

排查时用mpstat -I ALL 1观察每个CPU核的中断分布,能直接看出软中断是否集中在某一个核上:

mpstat -I ALL -P ALL 1

如果某一核的softirq特别高,而其它核很闲,基本可以断定是网卡队列中断绑核不均。解决办法要么是启用RPS(Receive Packet Steering)让软中断分散到多核,要么调整中断亲和性。这块调整时要小心,生产环境改动前先确认网卡驱动和内核版本是否支持。

下表是我整理的CPU类故障快查表,方便事后对照:

现象核心指标优先使用的命令高概率根因
load高但CPU idle高b列高、wa高vmstat、iostat磁盘IO阻塞
用户态CPU高us高、sy正常top、pidstat、perf top死循环、GC频繁、密集计算
内核态CPU高sy高、si高perf top、mpstat系统调用频繁、软中断过高
上下文切换爆炸cs高、线程大量切换vmstat、pidstat -w锁竞争、线程池过载
单核打满其它核空闲单核si高mpstat -I ALL网卡中断不均衡

CPU这块我特别强调一个心得:不要看到CPU高就急着调代码或扩容,很多时候问题出在外部依赖上,比如第三方接口慢导致线程池被占满、线程大量空转等待,CPU一样会难看。先看进程在干什么,再看该不该动刀。

3. 内存故障排查:别一看到占用高就喊OOM

内存问题比其他三类更容易“看起来严重”,因为free一敲,used数字吓人。但Linux的内存管理哲学是“内存闲着也是闲着,不如拿来当缓存”,所以判断内存是否真的不够,不能只看used,要看available和Swap的走势。

3.1 真正读懂free输出,理解buff/cache的意义

先看一条常规的free -h输出:

total used free shared buff/cache available Mem: 15Gi 8.1Gi 1.2Gi 210Mi 6.1Gi 6.4Gi Swap: 0Gi 0Gi 0Gi

这个系统物理内存16G左右,used已经8.1G,free只有1.2G,乍一看是不是很紧张?但available还有6.4G,说明实际可分配给新进程的内存远没有表面那么悲观。available指标是内核根据当前回收缓存的能力估算出来的“可用额度”,是比used可靠得多的判断依据。

buff/cache占了6.1G,这是Linux故意为之的——把空闲内存拿来做页缓存(page cache),加速文件读写。当应用真正需要内存时,这些缓存会被内核自动回收。所以遇到内存紧张,先别急着重启服务清缓存,看看/proc/meminfo里的MemAvailable是多少,再拍板。

判断内存是否真不够,我长期跟踪两个信号:一是available长期低于总内存的10%,二是Swap开始持续写入。前者说明内存是真的入不敷出,后者说明已经发生交换,访问性能会被明显拖累。

3.2 Swap与OOM Killer:当内核忍不住“杀人”时

要让OOM Killer介入,说明系统内存已经彻底耗尽,新进程连内存都分配不出来,内核必须挑选一个进程“开刀”。很多人的第一反应是“为什么杀的是我的Java服务”,想不通。这里要理解一个机制:每一个进程都有个oom_score,评分越高越容易被杀。占内存越多、存活时间越短、优先级越低的进程,评分越高。

当怀疑发生过OOM时,查证据比猜原因靠谱得多:

dmesg -T | grep -i -E 'out of memory|killed process'

输出的日志会明确告诉你某个进程占了多少内存、内核因为什么阈值触发、最后杀了谁。有一次我排查某后端服务的半夜重启,dmesg日志里清晰地写着某个新上线的监控采集进程吃光了内存,OOM倒霉地落在了业务进程头上——那个采集进程每天还在涨,典型的泄漏。

处理OOM,业界比较成熟的手段是配置systemd-oomd或者在服务里设oom_score_adj保护核心进程,但我还是建议从内存预算角度根治:要么给进程设置合理的-Xmx上限,要么排查泄漏点,而不是靠运气赌内核“杀得好”。

3.3 内存泄漏的识别与常用分析方法

内存泄漏在C/C++服务和Java服务里的表现不一样。C/C++服务通常是进程RSS持续爬升,长期不回落;Java服务受-Xmx限制,堆内泄漏表现为频繁Full GC,堆外泄漏则表现为RSS不断上涨。

判断泄漏我有一套固定动作。首先用ps aux --sort=-rss看哪个进程RSS数据异常地高,接着长时间跟踪:

ps -eo pid,rss,comm --sort=-rss | head -n 10 pidstat -r 1 10

如果PID的RSS在无人访问的凌晨时段依然只涨不跌,那泄漏基本实锤。接下来就是摘内存快照,Java用jmap,C++用valgrind或gdb抓分配栈。注意线上环境抓快照会触发进程停顿,尽量在低峰期做,或者用jmap -dump:live只抓存活对象减少体积。

有一个极易被忽视的“伪泄漏”:内核的slab内存越涨越高。通过slabtop或cat /proc/meminfo里的SReclaimable和Sunreclaim观察,老版本内核在特定场景下slab缓存不回收,会表现为“内存凭空消失”。这种问题常和内核版本、文件系统缓存结构有关,升级内核或定期drop_caches可以缓解,但根本解法还是找到哪个内核子系统在积压对象。

下表是内存排查常见场景的定位手段:

症状分析手段关键信号常见原因
available持续走低free -h、meminfoMemAvailable节节败退业务内存需求膨胀、泄漏
不明进程被杀dmesg -TKilled process字样系统级OOM、cgroup限额
Java进程内存高jmap、gc日志Full GC频繁堆设置过大、内存泄漏
RSS涨但堆不大pmap、jcmd堆外内存持续增直接内存、JNI泄漏
内核slab异常slabtopSUnreclaim暴涨内核数据结构溢出

内存排查有一个朴素但管用的原则:先看走势,再下结论。单个时间点的used高代表不了什么,连续几天的趋势曲线才说明问题。

4. 硬盘与文件系统故障排查:误删文件救不回来才是真疼

磁盘故障在四类问题里最“磨人”,因为它的表现五花八门:可能是空间满了、inode耗尽、IO延迟爆炸、文件系统只读、甚至删了文件之后空间还不释放。每一类症状的背后逻辑都不一样,我把常见的都拆开讲一遍。

4.1 空间满了还是inode满了,第一步先分清

df -h显示空间满了,大家都会查,但有个经典场景:df -h明明显示/只剩几百M,可是你用du -sh /一顿扫描,发现加起来才几十G,空间“不翼而飞”了。这种诡异现象多半是有文件被进程删除了但句柄没释放,也就是“占着茅坑不拉屎”。排查命令很简单:

lsof +L1

+L1会列出所有link count为0但仍被进程打开的文件,看完你可能会吓一跳:一个被删掉的日志文件还占着8G空间,源头的进程没重启,空间永远不会释放。处理办法要么kill对应进程,要么truncate那个已删除但还在写的文件句柄。

还有个常见坑是inode耗尽。表现是df -h显示还有空间,但应用报错“No space left on device”。这种情况一定是小文件把inode吃光了,用df -i一眼就能确认。曾经某应用服务器大量生成微型缓存文件,最终100%占满inode,清理旧文件后恢复。教训是:监控不但要盯空间,inode也要纳入日常巡检。

4.2 IO延迟与吞吐问题,区分是“堵了”还是“慢了”

磁盘IO的性能故障,用iostat -x 1 3是最直观的。重点看两个指标:%util和await。

%util表示设备有多忙,await表示IO请求从发出到完成的平均等待时间。通常机械硬盘的await超过50ms、SSD超过10ms,就要警惕IO性能劣化。这里面藏着一个新手最容易误解的点:%util达到100%不等于磁盘“坏了”,只说明IO请求已经满负荷排队,这时候你要思考的是这些IO请求是业务正常高吞吐,还是出现了低效的小块随机读写把设备拖垮。

想要定位到具体是哪个进程在制造IO压力,需要配合iotop或者pidstat -d:

pidstat -d 1 5

曾经排查某数据同步服务,线上%util飙到98%,用pidstat -d一查就是它在做高频随机读写,而业务本身对该磁盘的依赖完全可以通过加缓存消除。找到了源头,优化策略才有意义。

4.3 文件系统只读、硬件掉盘与损坏修复

文件系统突然变成只读,通常意味着内核检测到写入异常,为了防数据损坏主动“锁盘”。排查第一步看dmesg -T | tail有没有IO error、block error之类的内核报警。然后确认挂载状态:

mount | grep 'ro,'

如果确认只读,不要贸然remount,rw强行改回来,应当先判断是硬件问题还是文件系统结构问题。硬件层面检查SMART信息:

smartctl -a /dev/sda

看Reallocated_Sector_Ct、Pending_Sector这类计数,如果异常增长,基本可以判定硬盘快要退役了,应该立即迁移数据。文件系统结构出问题的话,在系统盘之外准备好救援环境,对/dev/sda执行fsck或xfs_repair。真到这一步,一定先备份再修复,这是死规矩。

磁盘故障的排查要点汇总如下:

表现第一步命令注意事项
空间满但不释放lsof +L1找已删除但未释放句柄的文件
有空间但报写满df -iinode耗尽,清理小文件
IO util高、await高iostat -x、pidstat -d区分正常饱和与异常低效IO
文件系统只读dmesg -T、mount先定因再处理,别盲目remount
磁盘硬件异常smartctl -a坏道计数持续增长建议换盘

还有一个我踩过很多次的坑:云主机厂商的磁盘监控告警和实例内看到的IO指标经常对不上,那是因为底层可能做了分布式存储的缓存或重定向。此时以业务响应时间和实例内指标为准,厂商那边的数据只作参考。

5. 网络故障排查:连通性、丢包、性能三线并行

网络是四类故障里链路最长、涉及设备最多的一类。一台服务器“网络有问题”,可能是内部网卡的问题,可能是机房交换机的问题,也有可能是对端服务本身的问题。排查网络时最忌讳的就是在单机上反复抓包,而不知道流量在上层的走向。

5.1 连通性排查:从本机到对端,一小段一小段地验证

网络连通性排查的基本功是分层验证。一条典型的验证路径是:先看网卡本身的状态,再看本机路由,然后逐跳探测到对端的路径。

ip link # 查看所有网卡link状态 ip addr # 查看IP配置 ip route # 查看路由表 ping <目标IP> # 测试基本连通性

如果本机IP都没有,或者网卡down了,后面什么都白谈。ping不通的时候,用traceroute或者mtr分段看看到底断在哪一跳。mtr是traceroute的增强版,它结合了ping和traceroute的功能,持续刷新每一跳的丢包率,我用它定位过好几起跨机房链路闪断的问题。注意:很多路由设备出于安全策略会丢弃ICMP包,导致某些跳显示???,但后续跳仍然能通,这不一定代表故障,需要结合是否真的丢包来判断。

5.2 端口与连接状态:TIME_WAIT、SYN_RECV、连接数爆满

服务越来越慢,用户连不进来,这类问题八成与连接状态相关。ss命令是查看套接字状态的利器:

ss -s # 汇总当前连接状态 ss -tan state time-wait | wc -l # 统计TIME_WAIT数量 ss -tan state syn-recv | wc -l # 统计半连接数量

大量TIME_WAIT是短连接服务的高并发后遗症,属于正常现象,但数量如果超过几十万,会占满端口和内存。常规调优手段是开启net.ipv4.tcp_tw_reuse并设置合理的tcp_max_tw_buckets。大量SYN_RECV就危险了,通常意味着SYN Flood攻击或者对端不回包,需要结合抓包确认。

另外提醒一句:修改内核网络参数时,不要从网上抄一段“调优大礼包”就整个贴进去,很多参数在不同内核版本下行为差异很大,改完要压测验证,否则可能解决一个问题引入另一个问题。

5.3 抓包定位真实丢包点:tcpdump + 网卡错误计数

网络性能问题最隐蔽的是“丢包”,表面看连接还在,但速度极慢、报错随机。丢包发生时,先用网卡的硬件计数定位大概方向:

ethtool -S eth0 | grep -E 'drop|err|discard'

如果rx_dropped或rx_missed高,说明数据包在网卡驱动层就被丢弃了,常见原因是环形缓冲区太小或CPU处理不过来,这时候调整ethtool -G eth0 rx 4096往往立竿见影。如果硬件计数正常,丢包可能发生在链路中间,就得抓包验证了。

tcpdump -i eth0 -nn host <目标IP> -c 100

我一般会同时抓客户端和服务端两端包做对比,看有没有TCP重传、乱序、重复ACK。tcpdump的输出里出现大量的[TCP Retransmission],基本可以确定端到端的链路质量不行,或者中间设备的缓冲区有丢包。这个结论能帮你把“背锅”对象理清楚,是网络架构的问题就别让应用开发去代码里找半天。

网络故障的排查命令速查如下:

目标命令重点信号
链路状态mtr -n某跳持续丢包/延迟飙升
连接状态分布ss -s大量TIME_WAIT或SYN_RECV
网卡丢包ethtool -S eth0rx_dropped、tx_dropped计数
抓包分析tcpdump -i eth0 -nn -c 100重传、乱序、RST异常
带宽占用iftop -i eth0找到占带宽的IP和端口

抓包时要特别小心:在流量极大的生产网卡上直接抓包可能把系统拖垮。控制好-c的数量,或者干脆把tcpdump的输出写到文件、用-s 128只抓每个包的前128字节,这样既能拿到必要信息,又不至于让抓包本身成为事故。

6. 常见问题速查与一套顺手的工作习惯

写到这里,核心的四类故障都拆完了,但我还是想把平时值机积累的一些“杂碎”经验一并分享。这些内容虽然不如上面的章节系统化,但都是实打实能救急的东西。

6.1 典型故障场景的对照速查表

日常值班最怕的就是“一时想不起来该用什么命令”。我把高频故障场景做成一张表,贴在个人笔记里,也方便排查时快速定位方向:

故障特征第一优先级排查常用工具大概率结论
服务超时、卡顿看CPU/IO哪个先爆top、vmstat、iostat资源瓶颈到底在哪一层
凌晨批量任务跑挂看OOM和磁盘空间dmesg、df -h内存超额或空间不足
定时任务不执行检查crontab和系统时间crontab -l、timedatectl时间漂移、配置错误
端口反复重启查看进程退出码systemctl status、journalctl启动参数或依赖不满足
程序变慢但不报错排查网络重传和丢包ss -s、tcpdump链路质量劣化

表里的内容不是让你背下来,而是建议你把它放在手边。真的出问题时,先按表里对应行去验证,每一步都留下输出记录,会比东一榔头西一棒子高效得多。

6.2 排障时要养成记录和保存现场的习惯

有几个习惯我在几次严重事故后彻底改掉了:

第一,开排障就开启script记录。script /tmp/debug_$(date +%s).log可以把终端所有输出存下来,事后拿来复盘或写报告,比手机拍照命令行界面强一百倍。

第二,确认系统时间同步。对比日志、对比tcpdump输出时,如果服务端和客户端时间相差几十秒,定位问题的难度直接翻倍。timedatectl看一眼,再决定要不要调整chronyd或ntpd。

第三,别在未确认根因的时候反复重启服务。很多老手都吃过这个亏,一看到内存紧张就顺手重启Java进程,内存是立刻释放了,但泄漏的根因没找到,过几天又来一轮。正确做法是先保留现场,把dmesg、free快照、进程堆栈、gc日志都留档,再考虑恢复业务。

第四,尽量用“查看”代替“改动”。在环境变量PS4开启set -x或者在命令前加echo做演练,能不动系统参数就不动。尤其对生产服务器,任何改动都要有“可回滚”的方案,比如先备份sysctl配置再修改。

个人在做了这么多年的Linux故障排查之后,最大的体会是:工具永远在更新,系统版本永远在变,但“定位问题的思维方式”是长期有效的。遇到再陌生的报错,只要沿着“确认现象 → 画指标画像 → 锁进程 → 追根因”这条路走,百分之八十的问题都能被稳定地缩小到一个可控范围内。剩下的百分之二十,靠的就是平时积累的记录习惯和对系统原理的扎实理解——这些都不是临时抱佛脚能补来的。希望这份整理能让你在下次接到线上告警时,少几分慌张,多几分从容。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询