做了这么多年Linux系统运维,我最常被同行问的一句话就是:“有没有那种比较全的故障排查资料,最好一册在手、天下我有那种。”每次看到有人晒出“191页Linux系统运维故障排查手册”,我都会认真点开翻一翻。不是因为这些手册本身有多神,而是这类资料背后代表的诉求特别真实:系统运维、故障排查这件事,光靠零散经验撑不起安全感,你需要一套能在半夜两点把人从床上拉起来之后、依旧能稳住手脚的方法论。今天我不打算替任何手册做宣传,而是结合这些年实打实踩过的坑、救过的火,把Linux系统运维故障排查里真正通用的那部分逻辑拆开讲清楚。如果你正准备系统整理自己的排查能力,或者遇到问题只知道重启但不知道下一步看什么,这篇文章值得你花十分钟读完。
先聊聊我对手册类资料的态度。191页的东西,知识点一定很全,但全不等于能用。故障排查的本质不是背命令,而是建立一套“现象到根因”的映射思维:看到这个症状,第一时间想到哪几个可能性,按什么顺序验证,哪些数据必须在现场采集,哪些操作做了反而会破坏现场。这一整套动作,才是运维的核心手艺。下面我按自己的实战习惯,把整个排查体系拆成几个部分,每一部分都配上实际案例和可以直接拿走的命令组合。
1. 排查前的准备:比命令更重要的四件事
很多人拿到的第一本故障排查书,翻开就是top、free、df、netstat,好像会敲这些命令就是排查高手。真到了生产环境你就会发现,命令只是最后执行的那一下“手”,真正决定排查效率的,是动手之前脑子里有没有一张地图。
1.1 先有“回滚”和“退出”方案,再动手
这是我在生产环境里吃过大亏之后总结出的第一条铁律。有一次线上MySQL实例hang住,连接数被打满,我当时的第一反应是立刻重启数据库。结果重启之后InnoDB做崩溃恢复,花了四十多分钟才把redo日志应用完,业务中断时间远远超过预期。后来复盘时发现,当时完全有更温和的手段:先抓取show engine innodb status的输出,再做一次快照备份,最后才考虑重建或切换。
所以我现在的习惯是:任何一台服务器出故障,先问自己三个问题——这个操作会不会造成数据丢失?操作能不能回退?如果操作失败,我还有没有备用路径?这三个问题答不上来,哪怕我看着像很着急,手上也会先停一停。尤其在多节点架构里,“先切流再排查”往往比“原地修复”更快恢复业务,这需要平时就把切换预案准备好,不能等事故发生了再临时想。
1.2 用基线数据建立“正常值”记忆
故障排查时最棘手的问题不是“哪里不对”,而是“和什么比不对”。比如一台服务器的load average常年0.3,某天突然变成2.5,这并不一定代表故障,可能只是业务高峰。反过来,如果一台服务器load average一直很高,但某个时刻突然降到了接近0,这可能意味着业务进程已经不在处理请求了,这种“假正常”更危险。
我在每台重要服务器上都会保留一套监控历史,至少覆盖CPU、内存、磁盘、网络、进程数、TCP连接数这几项。平时业务平稳的时候,随手记一下“这台机器日常水位是多少”,把这些数字和业务时段对应起来。真到排查的时候,这些数据就是判断当前指标是“异常”还是“常规波动”的坐标轴。没有基线的排查,只能靠猜;有基线,问题范围就能砍掉一大半。
1.3 标准化处理流程:先止损,再定位,后根治
要说运维事故中最高频的失误,就是对故障级别的误判。一个磁盘只读的故障,如果只是一块数据盘出了问题,可以先通过缩容或停掉非核心进程止损;但如果根分区满了,业务进程可能直接崩掉,这时就要先清理pid文件、旧日志,把空间腾出一点,才能让服务先拉起来。
我自己的习惯是把处理流程分成三层,先是“止损层”:确认有没有正在写入的数据、需不需要立刻切流或拉闸,核心目标是让损失止住;然后是“定位层”:等现场情况稳住之后,再来分析日志、指标、系统状态,找到真正的原因;最后是“根治层”:补监控、修配置、完善预案,确保同类问题不会再次发生。这三层绝对不能乱,尤其是前两层。很多新人在系统还在高负载、业务还在报错的时候,就拿着抓包工具去分析应用日志,结果等日志分析完了,业务已经挂了半天。
1.4 建自己的故障笔记,而不是收藏别人的手册
191页手册也好,网上几千条的故障案例也罢,它们只能帮你补盲区,真正能让你在事故现场做出快速判断的,是你自己大脑里沉淀的那些“同款坑”。我在自己的笔记里记录每个案例时,永远包含四个字段:故障现象、根因分析、修复动作、如何避免。特别是“如何避免”这一步,我会逼自己写到配置项、脚本或者监控阈值这个级别,而不是写一句“以后注意”。
有一次一个新来的同事处理Nginx 502,他在手册上找到了“看error.log”这一步,看完日志之后没能定位到上游超时,反而去反复调worker_processes参数。其实他缺的不是命令,而是没有在笔记里建立起“502是上游不响应,不是Nginx本身配置问题”的判断线。后来我把这类关联思考都整理进了团队的故障手册,再遇到502,大家的第一反应就变成了检查PHP-FPM或后端应用的负载和超时配置。
2. 高频故障场景:标志性症状与第一反应
故障场景千千万,但落到Linux服务器上,绝大多数问题都能归进几个常见大类:硬件与虚空化层、系统层、网络层、应用与持久化层。每一类都有自己标志性的症状组合,也能映射到固定的第一反应命令。掌握这种对应关系,排查效率会明显提升。
2.1 硬件与虚拟化层:不是所有故障都会先在业务侧暴露
物理服务器或者虚拟机最常见的隐藏问题是磁盘坏道和内存ECC报错。磁盘坏道的初期症状特别迷惑,应用可能只是偶尔I/O等待变高,数据库偶尔报一个检查点慢,如果不看dmesg,很容易当成普通磁盘性能问题去调I/O调度器。实际上,只要在故障机器上执行dmesg -T | grep -i "error|blocked|task hung",就能发现磁盘控制器在反复重试坏块。
内存故障更容易被忽略,因为Linux内核通常会把坏页标记掉,系统继续运行,偶尔出现一次无法解释的进程崩溃。如果你发现同一个进程莫名其妙隔几天就core dump一次,而且时间点没有任何规律,建议去看看BMC/SEL事件日志,或者执行mcelog --client检查硬件错误记录。曾有朋友的一台数据库服务器每隔一两周就发生一次实例崩溃,监控图上内存使用率也不高,排查了大半个月,最后发现是内存条有偶发要修复的单元,换掉之后问题立即消失。
2.2 系统层:从“能开机”到“能干活”,中间隔着一堆检查
“系统起不来”是每个运维早晚会遇到的事。我最常用的开机故障排查路径是:先看grub菜单里的内核版本和启动参数,确认默认启动项是否正确;如果卡在某个服务上,进入initramfs shell,检查根分区文件系统是否完整,xfs_repair或fsck能不能执行;如果文件系统没有问题,再看看驱动模块是否加载成功,尤其是磁盘控制卡和网卡的驱动。
系统层另一个高频故障是根分区空间用满。df -h一看100%,du一找是/var/log或/tmp下面的大文件。处理逻辑大家都懂,但我想提醒一个容易忽略的点:删除文件后空间不一定马上释放,如果有进程还持有被删文件的句柄,df看到的还是满的。这时候用lsof | grep deleted把对应进程找出来,确认是否可以重启该进程,或者在确认安全的情况下直接清空文件:: > /var/log/xxx.log,而不是rm掉一个正在被写入的日志文件。
2.3 网络层:延迟高不等于带宽小,入口排队才是元凶
我排查过的网络性能问题里,至少有三分之一跟带宽无关。比如应用觉得“网络慢”,实际表现是请求RT抖动厉害,但网卡流量并不高。这种情况下,第一反应应该看网卡软中断分布和各CPU的使用情况。用mpstat -P ALL 1观察是否有单个CPU被打满,再用top里的si字段确认软中断占比,如果是单队列网卡,数据流量集中到一个CPU核,就会产生“明明每个网卡只有一个中断号,数据却全部卡在一个核”的瓶颈。
出现这种问题时,可以考虑启用RSS(Receive Side Scaling),或者用smp_affinity把中断绑到不同CPU核心上。之前的实践里,在某台8核机器上做了一次均匀分散绑定,Nginx的SSL握手性能提高了近一倍,RT从几十毫秒降到个位数毫秒。这个案例想说明的是,很多时候“网络慢”的根源不在链路,而在系统侧的接收处理能力。
2.4 应用层与数据库:日志先行,慢查询指标是锚点
应用故障的排查,我最反对一上来就重启。重启会把进程内存里的现场全部清掉,很多中间状态再也无法获取。正确顺序应该是:先看应用自己的日志,再看进程的状态,然后才是系统层的关联证据。比如Java应用内存问题,先看一下GC日志和堆转储是否已经保留,再执行jstat -gcutil和jmap -dump,把现场留足,再采取重启动作。
数据库方面,慢查询日志永远是最重要的第一依据。MySQL可以用slow_query_log记录执行时间超过阈值的SQL,再配合explain查看执行计划,大多数性能问题都能定位到全表扫描或者索引失效。很多团队在业务高峰期才打开慢查询日志,这是很可惜的,因为高峰期的压力问题恰恰需要在平时积累基线。把这个开关常开,阈值设置在100毫秒,不仅能定位问题,还能反向推动研发优化SQL。
3. 排查常用命令的“实操经验修正版”
很多人列Linux命令大全能列几百个,但真到了排查现场,常用的也就是二三十个。关键是,这些常用命令都有一些使用细节,光看手册看不出价值,我在下面列几个经历过实战考验的用法。
3.1 top、free、df:指标要看,更要会看趋势
top有两个参数我几乎每次都用:top -c,显示完整命令行,能看出具体是哪个服务在占资源,而不是只看到java或nginx;top -Hp PID,查看某个进程内部每个线程的CPU占用,Java踩满CPU的时候,这一步能快速定位到具体线程,再用jstack把线程栈拿到,基本就能锁定问题代码。
free这个命令,很多人关注“可用内存”那一行,其实在Linux里,还有cache和buffer占了大量内存,真正的内存短缺判断要看available这一列的数值趋势。如果available持续走低,而buff/cache又居高不下,往往是某个进程在大量读写文件但没有及时释放缓存,这时不要急着清cache,先找出来是谁在写,否则清了也没用。
df时一定要带-h是一定的,但生产环境我习惯加df -hT,把文件系统类型也显示出来。不同文件系统在故障时的处理方式差异很大,xfs和ext4的修复命令完全不同,先确认文件系统类型再动手,能避免很多误操作。
3.2 systemctl、journalctl、dmesg:把启动日志养成一种习惯
排查服务无法启动时,不要只停留在“哦,服务起不来”这个层面。systemctl status xxx会给你第一层信息,但这往往是结果而不是原因。真正的原因在journalctl -u xxx -n 50里,或者systemctl status显示的Process: exec=那一行。举个例子,Nginx启动失败,如果你只看到“failed”而没有去看日志,很可能错过端口被占用的真正报错,因为Nginx的error.log里才会明确写出bind() to 0.0.0.0:80 failed。
dmesg更适合用来排查内核层面的问题,比如模块加载失败、硬件报错、OOM killer的行为。我见过最经典的OOM场景是,内存明明还有很多剩余,但进程被杀了,看dmesg才发现是cgroup的内存限制被触发,业务进程超出了容器设定的上限,而不是整机内存不足。这种问题不看内核日志,光在应用层调JVM参数完全是白费力气。
3.3 ss、tcpdump、iostat:揪出连接和块设备问题
ss命令替代netstat已经很多年了,我特别推荐两个组合:ss -s看系统整体连接状态的汇总,能快速判断TIME_WAIT或CLOSE_WAIT是否大量堆积;ss -tnp可以看到占用某个端口的进程PID。遇到CLOSE_WAIT堆积,99%是应用代码没有正确关闭连接,需要去查应用侧,而不是在系统上乱调参数。
tcpdump是最后手段,不要在还没有确认网络拓扑和数据流向的时候就开始抓包。先明确源IP、目标IP、端口、协议,sudu tcpdump -i eth0 host 10.0.0.5 and port 3306 -w /tmp/mysql.cap,把抓包结果保存成文件,再用wireshark分析。有一次排查两个机房之间的数据同步延迟问题,前后看了应用日志、数据库状态都没有发现异常,最后用tcpdump对比了两个机房间TCP重传比例,发现公网链路上偶发丢包才是元凶。
iotop和iostat是块设备方向的利器。iotop能看到具体是哪个进程在做大量I/O,iostat -x 1能显示每条磁盘的等待队列长度和利用率。注意,util达到100%并不能“直接”意味着磁盘满了,还需要看await和svctm的比值。如果util很高,但await很低,说明请求在设备端没有被阻塞,可能只是某一块盘请求过于密集;如果await明显高于svctm的几倍,说明I/O在排队,这才是真正的性能瓶颈。
4. 典型故障复盘:从现象到根因的三次回溯
知识归知识,真正有血有肉的经验一定长在那些深夜处理过的故障里。我挑三个非常典型的案例,把排查过程和思维转折完整写出来,这三个案例对应三种非常常见的故障形态:资源写满、网络偶发超时、误操作删除。
4.1 案例一:磁盘写满导致数据库只读
现象:业务突然大面积报错,提示数据库无法写入。登录服务器后,df -h发现根分区已经100%,数据库的报错日志里明确写着“Read-only file system”。
第一反应是什么?很多人会直接开始删文件。但我现在的第一反应是先用mount -o remount,rw /把文件系统重新挂载成可写,然后把数据库进程的数据目录临时切到另一块有空间的盘上,保证能先把业务恢复起来。现场操作时我找到/var/log里几个超过5GB的旧日志,先压缩再用rsync转移到备份盘,腾出一部分空间,接着把业务分区里的临时文件目录清理掉,数据库恢复可写。但这只是止损,不是根治。
复盘时我才发现,这台服务器的日志轮转脚本已经连续三天没有执行了。原因是脚本里用了find /var/log -mtime +7 -delete,但当时系统时钟因为NTP不同步快了20分钟,脚本执行时判断“今天还是未来”,于是把所有普通日志都当成当天文件跳过,导致日志越积越多。最终我调整了日志脚本的触发逻辑,在find命令里显式排除当日文件,并给日志目录加了inotify监控,超过阈值就告警。这个案例的关键启示是:磁盘满只是结果,日志管理策略的缺陷才是根因。
4.2 案例二:DNS解析慢导致接口偶发超时
现象:某个内部微服务的调用经常在高峰期偶发超时,每次持续几十秒到几分钟不等。查应用日志,服务端没有明显报错,数据库也没有慢查询,监控面板上CPU、内存都正常。
查网络侧,用ping测试对端IP,延迟一直很稳定。但进一步测试后发现,业务代码里访问某个内部域名时,偶发的延迟特别高。用dig对域名做解析测试,发现解析时间在几毫秒到几百毫秒之间剧烈抖动。原因定位到/etc/resolv.conf里配置了两个上游DNS服务器,其中一个因为跨机房网络质量差,经常丢包,glibc的解析器遇到超时会先等第一个服务器超时,再发起第二次请求,这个串行等待就变成了接口的偶发超时。
解决办法是把质量差的DNS服务器从解析配置里先移除,并调整了resolv.conf里的options timeout:1 attempts:1,避免解析器反复重试。另外,考虑到关键服务依赖内部域名解析,我在services侧加了DNS解析结果的本地缓存,业务代码不再每次请求前做一次完整解析。这类问题用系统命令查不出来,但排查思路是通的:任何偶发延迟,都要把依赖链路上每个环节的时间消耗拆开测量,而不是只盯着一台机器看。
4.3 案例三:日志清理脚本误删新日志
现象:早上一到办公室,同事反馈某台服务的数据目录被清空了。排查发现,前一天晚上数据目录里的文件被全部删除,只剩一个空的目录结构。
这个故障的原因非常反直觉:原本写好的日志清理脚本只在目录超过80%时才会执行,但在某次版本更新后,定时任务的执行用户和目录权限发生了变化,脚本里的变量值没有拿到绝对路径,变成了一条类似rm -rf ${LOG_DIR}/*的命令,而LOG_DIR在脚本错误条件下变成空字符串,结果就是rm -rf /*的灾难。
这个案例对我的教训非常大。虽然最终的备份能恢复大部分数据,但让团队反复讨论了很久:为什么允许生产环境存在一个可以直接rm -rf空变量路径的脚本?现在我们的规范是:所有涉及删除操作的脚本,执行前必须判断变量是否非空,在路径变量前加入哨兵前缀如/data/logs,并统一在命令中用--preserve-root参数做保护。修改后我会拿一台临时机器先跑一遍脚本,用set -x观察实际命令展开的路径,而不是盲目自信。事后我还给定时任务增加了执行结果回传机制,任何脚本执行异常都会通过监控发通知,而不是让错误悄悄发生。
5. 常见问题速查表:一眼定位问题方向
下面这张表是我在实际工作中反复用到的“症状到方向”对照表,放在手边随时可以查,适合打印出来贴在工位旁。
| 症状 | 第一优先检查项 | 常用命令 | 解决方向 |
|---|---|---|---|
| CPU居高不下 | 用户态还是内核态、单核还是整体 | top -c,mpstat -P ALL 1,pidstat | 用户态看应用线程,内核态看软中断和驱动 |
| 内存持续下降 | available值、OOM记录 | free -h,dmesg | grep -i oom,cat /proc/meminfo | 定位占用进程,检查漏释放或泄漏 |
| 磁盘空间满 | 大文件、被删未释放文件 | df -hT,du -sh *,lsof | grep deleted | 轮转日志,清理旧数据,定位持有句柄的进程 |
| 磁盘I/O排队 | util与await的关系 | iostat -x 1,iotop | 优化读写模式,更换硬件,调整队列深度 |
| 端口无法监听 | 端口占用、SELinux、防火墙策略 | ss -lntp,getenforce,firewall-cmd | 规避冲突,调整SELinux context或放行规则 |
| 服务启动失败 | 服务日志和systemd错误信息 | journalctl -u xxx -n 50,systemctl status | 定位配置错误、依赖缺失、权限问题 |
| 偶发连接超时 | DNS解析、网络重传、连接队列溢出 | dig +trace,ss -s,tcpdump | 调整域名解析策略、加大backlog、检查链路质量 |
| 文件系统只读 | 内核日志中的I/O错误 | dmesg | tail,mount | 修复磁盘或文件系统,必要时联系硬件侧 |
| TIME_WAIT堆积 | 连接关闭频率和keepalive配置 | ss -s,sysctl net.ipv4.tcp_fin_timeout | 调短TIME_WAIT,或调整应用连接复用策略 |
| CLOSE_WAIT堆积 | 应用未正确关闭socket | ss -tnp,jstack/pstack | 检查应用代码,核心是补上close或资源回收逻辑 |
| 进程OOM被杀 | cgroup限制、内存碎片、单进程内存暴涨 | dmesg | grep -i oom,cat /sys/fs/cgroup/.../memory.events | 调大限制、优化内存占用、拆分进程粒度 |
| DNS解析慢 | 上游DNS响应时间、本地缓存策略 | dig @8.8.8.8域名,cat /etc/resolv.conf | 调整resolv.conf重试策略,增加本地DNS缓存 |
这张表不可能覆盖所有故障,但可以帮你在面对未知问题时,先锚定到最可能的领域,然后再深入排查。
6. 把手册变成自己的“故障决策树”
每次看到XXX页的故障手册,我都会建议大家不要只当收藏家。手册提供了足够多的知识点,但运维最重要的能力是“决策”,而决策依赖的是树状结构:根据一个现象,列出所有候选根因,按发生概率和验证成本排序,逐个排除。我见过很多优秀运维,他们脑子里都有一套自己的决策树,遇到问题不会慌乱,因为每个分支都已经提前走过了。
构建自己的故障决策树,可以从三张清单开始。第一张是“症状清单”,记录你日常遇到过和从手册中看到的各类故障现象,每个症状写清楚:出现时的上下文是什么,哪些指标会是敏感指标。第二张是“命令清单”,为每个症状写下对应的验证命令,把命令的执行结果和“正常情况”写在一起,这样你才知道什么结果意味着什么。第三张是“动作清单”,每条动作都标清楚执行后的预期效果和回滚方式,比如“重启Nginx之前先保存error.log,验证当前配置nginx -t”。
举个例子,如果有人问你“一个服务突然变慢怎么排”,你可以按这样的顺序回答:先看系统负载和CPU状态,确认是CPU被占满还是I/O阻塞;再看应用日志,确认是慢请求还是报错重试;最后看依赖系统,比如数据库、Redis、外部API的延迟是否有变化。这个顺序就是把最大的可能性放在最前面,每一步都用数据验证,而不是一上来就凭感觉猜。
这一点也是我想特别强调的:手册可以教你每一个命令怎么用,但教不了你怎么把几十个命令串成一条高效的排查链路。链路这种东西,只能靠自己在实战中反复打磨。每处理完一起故障,我都会回头问自己一个问题:如果下周一同样的问题再次发生,能不能在五步之内定位到根因?如果回答“不能”,说明还需要把这个流程继续拆细、补充自动化脚本或监控指标。
7. 附录:我建议长期保留的运维习惯
顺着上面的思路,我再分享几个自己一直在坚持的运维习惯,这些习惯帮我省下了大量救火时间,也算是“手册之外”真正值钱的部分。
习惯一:给所有重要目录写一个“空间占用基线”。不是简单的df -h看一眼就完了,而是把每个分区的日常工作负载、增长速率、预计哪天会达到80%,都提前算出来。规划好日志轮转周期和清理策略,比等到满了再拆东墙补西墙靠谱得多。
习惯二:每周抽查一次系统日志中的error关键字。不用等到故障发生才去看,每周固定花十分钟,在每台核心服务器上执行journalctl -p err -since "-7 days",把里面的error逐条过一遍。很多故障在爆发之前,日志里已经积累了大量低级告警,早处理就是省掉未来的夜晚。
习惯三:建立统一的“现场采集脚本”。平常就把top、free、df、ss、dmesg、uptime、last、history这些信息的采集命令汇总到一个脚本里,发生故障时先跑一遍,把输出保存到/tmp/sos目录,作为排查和复盘的第一手现场数据。万一问题需要反复分析,这些数据比回忆靠谱得多。
习惯四:不要在生产环境直接查大表或频繁执行全量扫描命令。这句话看起来多余,但我见过太多人为了找一个大文件,在根目录跑了du -sh *,结果I/O飙高,影响了正常业务。先分析一下大概位置,用ls -lhS按大小排序定向排查,或者先看监控图,再决定是否全盘扫描。
习惯五:每个故障处理完,顺手把处理过程中的关键信息沉淀成一条笔记。不要等到项目小结或者年底复盘时再补,现场的记忆是最鲜活的。哪怕是半夜三点处理完的一次小故障,花五分钟记下来,三个月后回头看,这就是你最宝贵的经验库。
我个人在实际操作中的体会是,所谓“191页的Linux系统运维故障排查手册”,真正把它读透的方法不是从头到尾背,而是先把自己已经遇到的故障全部对上号,再去看那些工作中还没踩过的类型,把每条经验转化成自己的操作条件反射。手册能给你的是知识边界,而边界之内的判断力、决策速度和复盘能力,都需要在一次次的真实故障中打磨出来。希望这篇内容能帮你少走一些弯路,也欢迎你在自己的环境里把这些方法跑一遍,再按照自己的场景改造成适合自己的排查路径。