ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux运维故障排查:从CPU飙高到磁盘满的定位思路与实战方法

Linux运维故障排查:从CPU飙高到磁盘满的定位思路与实战方法 1. 遇到故障先别急着敲命令先建立“故障坐标系”做了这么多年Linux运维我最深的体会是排查故障最难的往往不是解决本身而是定位。就好比去医院看病医生不会上来就开药一定先问诊、再安排检查最后才下结论。系统出了问题第一步永远是搞清楚“病在哪个系统、什么表现、什么时候开始的”。很多人一拿到报错就急着翻搜索引擎或者群里发截图这样效率其实很低。我自己的习惯是花两到三分钟做一个快速分类把这个故障放进一个“坐标系”里。这个坐标系由两个维度组成影响范围是整个服务器挂了还是某个服务挂了还是某条网络链路不通这决定了从哪一层开始查。故障类型是性能问题慢、卡、CPU高还是可用性问题宕机、进程退出、端口不通还是数据问题文件丢失、磁盘满、权限错乱判断完这两个维度一个初步的排查方向就出来了。比如“所有用户访问网站都超时”和“单个用户在特定时间段访问慢”这两者的排查路径是完全不同的。前者要先看网络入口、负载均衡、后端实例存活后者可能要查数据库慢查询、中间件线程池甚至要怀疑是不是本地网络的问题。还有一点我踩过不少坑变更与故障的强关联。大多数故障都不是无缘无故的。排查前先回想一下系统最近有没有动过什么装过什么包、改过什么配置、升级过什么内核、调整过什么防火墙规则、做过分区扩容哪怕只是觉得“可能顺手改了一下”也要当作一个重要线索去确认。我见过太多案例最后定位到根因的时候才发现就是前一天随手改了一个参数或者重启了一个依赖服务导致的。把变更历史整理清楚排查时间能缩短一半以上。有了这个大方向接下来的每一条命令、每一次排查才是有的放矢的。下面我会按照Linux系统中最常见的几类故障场景把完整的排查链路和工具用法展开讲清楚。2. 系统起不来别慌从开机日志一步步往回推服务器起不来是所有运维最不希望遇到的场景因为影响面最大。但这类问题恰恰是最有规律可循的因为Linux的启动流程是固定的——固件、引导加载器、内核、init进程、服务启动。只要按照这个链路一层层往回排查基本都能找到问题所在。2.1 启动卡住的位置就是问题所在的方向标开机过程中如果卡住了注意观察卡在哪个阶段如果是硬件自检阶段就卡住或者直接黑屏那大概率是硬件问题比如内存条松动、硬盘掉盘、电源供电不稳。先去机房或者通过带外管理看硬件状态指示灯。如果过了自检但在GRUB引导界面卡住多半是引导配置损坏或磁盘分区表异常。如果内核已经开始加载屏幕上滚过了很多内核日志然后卡死那要重点看最后几行输出通常内核会打印出它卡在哪个驱动或哪个设备上。如果是内核加载完成但进入systemd阶段后卡住那可能是某个关键服务启动失败或者在等待超时。判断出大致位置之后再看日志就有的放矢了。很多故障重启一次复现不了就是因为没在卡住的当下抓住现场。2.2 进入救援模式的几种手段系统起不来不代表救不回来。最常见的办法是使用系统安装盘或者Live CD进入救援模式。具体步骤如下通过带外管理如iLO、iDRAC、IPMI挂载系统安装镜像从光驱或虚拟光驱引导。在安装界面选择“Troubleshooting”然后选择“Rescue a CentOS system”或类似选项。选择已安装的系统分区通常会挂载到/mnt/sysimage目录。执行chroot /mnt/sysimage进入原系统环境。此时就可以检查和修复引导问题了。进去以后的排查动作按优先级来# 查看磁盘分区和挂载情况确认系统盘是否正常识别 fdisk -l lsblk # 检查GRUB配置文件 cat /etc/default/grub ls /boot/grub2/ # 重新生成GRUB配置在chroot环境下 grub2-mkconfig -o /boot/grub2/grub.cfg修复引导是救援模式里最高频的操作。很多情况就是更新内核或者意外修改了GRUB配置导致的。还有一种场景是磁盘满了导致系统启动时无法写入临时文件或日志表现为启动卡住或反复重启。在救援模式下先清理临时目录比如/tmp里的旧文件释放空间后再重启问题往往就解决了。2.3 dmesg和journalctl是启动故障的“黑匣子”如果系统能起来只是起来的过程很慢或者有服务报错那第一手资料就是内核日志和系统日志。# 查看内核环形缓冲区日志重点看硬件错误、驱动加载失败、文件系统报错 dmesg -T | grep -Ei error|fail|warn|usb|scsi|block | tail -50 # 查看本次启动之后的系统日志 journalctl -b我遇到过一台机器每次开机都要等三到五分钟才能ping通。用dmesg一查发现是某个网卡驱动反复加载失败一直在重试最终靠备份驱动才起来。这类问题不做日志分析根本找不到原因。journalctl有几个常用参数值得记一下# 查看上一次启动的日志适用于系统重启后排查上一次启动的故障 journalctl -b -1 # 查看指定服务的启动日志 journalctl -u nginx.service # 按优先级过滤只显示错误及以上级别 journalctl -p err -b排查启动类故障我的原则是先确认能启动到哪一步再决定从哪个环节入手绝不盲目重装系统。重装是最不得已的办法因为它会丢失现场、丢失数据而且如果网络配置和分区方案是手工做的重装本身的成本和风险也很高。3. 应用卡顿和CPU飙高CPU视角的排查工具链服务器CPU飙到100%或者load average居高不下是运维每天都会遇到的高频问题。这一节我不打算简单堆命令而是想把排查的思路讲清楚——CPU高只是一种现象背后的原因可能差别很大。3.1 先分清是“计算密集”还是“进程异常”拿到一台CPU高的机器第一步不是杀进程而是看整体情况。# 查看系统负载1分钟、5分钟、15分钟的平均负载 uptime # 按CPU占用排序查看进程 top -c这里有一个新手容易绕进去的坑load average高并不一定代表CPU忙。它统计的是处于可运行状态和不可中断睡眠状态的进程数。如果进程大量卡在磁盘IO上不可中断的D状态load也会飙高但CPU的使用率可能并不高。你跑top看%Cpu(s)里的wa列如果这个数字很高说明磁盘IO才是瓶颈。所以拿到一台“卡顿”的机器第一件事是先看四个数字load average、CPU使用率、waIO等待占比、内存是否吃紧。这四个数字能帮你快速判断这是CPU瓶颈、IO瓶颈还是内存瓶颈。3.2 线程级定位找出真正吃CPU的代码路径如果确认是CPU占用高接下来要精确到进程里的线程再到线程对应的代码或系统调用。这一步才是真正的深度定位。# top进入后按H键切换为线程模式就能看到每个线程的CPU占用 top -H -p PID # 把指定进程的线程占用情况排序输出 top -H -b -n 1 -p PID拿到线程ID后把它转成十六进制# 假设线程ID是 12345 printf %x\n 12345这个十六进制数字后面有大用。如果Java应用可以配合jstack打印线程栈然后去栈文件里搜这个十六进制nidjstack PID /tmp/thread_dump.txt # 在thread_dump.txt中搜索 nid0x3039如果是C/C或者Python的GIL问题用perf会更直接# 以指定进程为目标采样CPU调用栈 perf top -p PID # 采样结束后生成火焰图数据 perf record -g -p PID -o /tmp/perf.data perf script -i /tmp/perf.data /tmp/perf.unfoldperf这样用的意义在于能直接看到CPU时间花在了哪个函数上而不只是看到哪个进程占用高。实战中我遇到过PHP-FPM进程CPU高用perf一看发现是某个扩展的哈希函数在极端数据分布下退化了根本不是业务代码的问题。如果只看top最多只能定位到PHP-FPM这个进程根本没办法继续往下挖。3.3 CPU排查的经典场景从现象到结论我整理几个高频的实际场景大家可以对照着看。现象初步判断进一步排查工具单个进程CPU持续100%可能是死循环或CPU密集计算top -H定位线程perf采样看调用栈load很高但CPU不高磁盘IO或内存换页导致D状态进程iostat、vmstat、pidstat -dCPU使用率忽高忽低可能是定时任务、流量突增、日志刷得太猛crontab -l结合业务访问日志排查多核机器单个核打满可能是单线程应用或者锁竞争mpstat -P ALL观察是否单核爆满用户态CPU高业务代码本身计算密集gdb attach、perf top内核态CPU高sy列高系统调用频繁、上下文切换开销大pidstat -w、perf top查看内核函数这里说一个我个人常用的“三板斧”习惯遇到CPU问题先跑这三条命令基本能确定大半# 1. 按CPU占用排序先看是哪个进程 ps -eo pid,ppid,%cpu,%mem,cmd --sort-%cpu | head -20 # 2. 看这个进程的线程分布 top -H -p PID # 3. 再看这个进程在等什么上下文切换和等待情况 pidstat -w -p PID 1 5CPU问题的排查核心思维就一句话从进程到线程从线程到调用栈从调用栈到代码行。每一步都是缩小嫌疑范围的过程而不是盲目杀进程重启。杀进程是最快的“解决”但如果不定位到根因同样的故障明天还会来而且你仍然不知道为什么。4. 磁盘满了不等于没空间那些“假满”和真满的区分磁盘告警是运维最常见的告警之一。df -h一跑使用率100%但奇怪的是du统计下来的所有文件大小加起来和分区大小对不上。这种时候你如果按照“删大文件”的思路去处理可能忙活半天磁盘空间一点也没释放。4.1 为什么文件删了空间却没释放这是网上被问爆了的问题也是运维必踩的坑。在Linux中如果一个文件被删除但仍有进程在持有它的文件描述符那么文件的inode和磁盘块并不会立即释放。也就是说从文件系统层面看这个文件已经不存在了ls、du都看不到了但空间被那个进程占着直到进程关闭文件描述符或者进程退出。遇到df满了但du找不到大头的情况第一反应应该是# 找出所有已删除但仍被进程占用的文件 lsof | grep deletedlsof输出的第一列是进程名第二列是PID最后一列会标(deleted)。看到了吗就是这个进程在占用空间。解决办法分两种如果这个进程可以重启重启进程空间就会释放。如果进程不能重启就要看是哪个日志文件被删了需要想办法在不中断服务的情况下处理。这种问题最典型的场景是应用把日志写到/var/log/app.log运维手工执行了rm /var/log/app.log但应用进程还开着这个文件的句柄日志仍在写入这个已经被删除的inode于是磁盘空间只增不减前端的表现就是“日志文件没了但磁盘空间还在减少”。正确的清空日志方式应该是# 用重定向清空文件而不是删除文件 /var/log/app.log # 或者更优雅的方式 truncate -s 0 /var/log/app.log这样文件在文件系统层面仍然存在inode不变进程持有的文件描述符不会被破坏空间也释放了。这个小细节值得每个做运维的人都刻在脑子里。4.2 inode耗尽还有空间但什么都写不进去另外一个反直觉的“满”是inode满。出现“No space left on device”的报错可是df -h一看明明还有几个GB。这时候要看的是inode使用率df -iinode是文件系统维护文件元数据的数据结构。每个文件或目录都要占用一个inode。如果小文件特别多inode会被消耗光哪怕数据块还有很多剩余系统也无法再创建新文件。最常见的元凶是邮件队列、session文件、临时目录里堆积了海量小文件还有某些应用疯狂写缓存文件但从不清理。处理办法也很直接找到那个目录把过期文件清掉。# 找到inode占用最多的目录 for dir in /var/* /tmp /home; do echo $dir: $(find $dir -xdev -type f | wc -l) done # 快速清理一个目录下N天前的文件 find /var/spool/clientmqueue -type f -mtime 7 -delete还有一个技巧遇到某些目录文件多到ls都卡的现象时不要用ls去数文件直接用find | wc -l或者ls | wc -l也会很慢。可以用find /dir -maxdepth 1 -type f | wc -l但还是慢。这种极端情况下只能靠目录内部结构来定位比如按时间分批删或者干脆用rsync配合--delete的方式同步一个空目录过去清理速度会快很多。4.3 磁盘IO慢和“只读文件系统”还有两类磁盘场景需要单独说。第一类是IO延迟高。磁盘性能问题的排查思路是看队列长度、等待时间、利用率这几个指标# iostat查看各设备的IO情况重点是await、svctm、util iostat -x 1 5 # 定位哪些进程在大量读写磁盘 iotop -outil如果长期接近100%说明磁盘确实在满负荷工作这时候要考虑升级存储、加缓存或者排查业务上是否有不合理的IO模式。如果util不高但await很高那可能是磁盘本身有坏道、RAID降级重建或者同一块盘上有其他虚拟机在抢IO。这些情况靠iostat能看出一些端倪但最终可能需要结合存储侧的状态来确认。第二类是文件系统变成只读。这个现象一出现基本上意味着文件系统发现了严重的元数据错误或者硬件层面的IO错误。系统会把文件系统挂载为只读来防止进一步损坏。# 查看挂载状态ro表示只读 mount | grep / # 查看内核日志通常会有ext4或xfs的报错 dmesg -T | grep -Ei ext4|xfs|I/O error|remount如果确认是硬件故障先备份能备份的数据然后联系硬件厂商处理。如果是意外断电导致的文件系统脏状态可以在救援模式下执行文件系统检查和修复。但这里要特别提醒修复文件系统前一定先做好数据备份或者镜像因为fsck这类工具在极端情况下也可能导致数据进一步损失。# 以只读方式检查文件系统先看有没有错误 fsck -n /dev/sda1 # 确认无误后再执行修复 fsck -y /dev/sda1我个人的习惯是能通过挂载副本或者快照检查的绝不直接对原盘做修复。云环境就先打快照物理机就先做磁盘镜像。5. 网络不通的排查顺序从网卡到DNS层层拆解网络故障排查最大的陷阱是没有顺序。一会儿ping一会儿看路由一会儿查DNS结果乱成一锅粥。网络是分层的排查就应该严格按照从底层到顶层的顺序来物理链路、链路层、网络层、传输层、应用层。5.1 最笨但最有效的排查三步曲我自己不管遇到多复杂的网络问题都先跑这三个命令# 第一步确认本机IP和网卡状态 ip addr show # 第二步确认默认路由和网关 ip route show # 第三步确认到网关的连通性 ping -c 4 网关IP这三步能解决90%的“无法上网”“外网ping不通”类问题。如果IP不对说明DHCP没获取到地址或者配置错了。如果路由不对说明默认网关缺失或者配置错了。如果网关ping不通说明下面一层出问题了——物理网线、交换机端口、网卡驱动、防火墙。在这里有一个新手容易踩的坑服务器有两个网卡一个内网一个外网。默认路由经常会被配错导致内网走外网网关外网流量又回不来。这时候就要借助策略路由或者配置静态路由解决。判断方法是# 查看到目标IP的具体路由走向 ip route get 8.8.8.8这条命令会告诉你实际访问某个IP时系统会从哪个网卡、走哪个网关出去波段适合做路由排查。5.2 服务端口通不通telnet、nc、ss三件套网络层通不代表应用层通。很多时候我们要确认一个服务端口是否在监听是否能从外部访问会用到这几条# 查看本机监听端口和服务 ss -lntp # 从本机测试端口连通性 telnet 192.168.1.10 3306 # 从远端测试端口连通性好用telnet不可用时用nc nc -vz 192.168.1.10 3306ss是netstat的替代品输出更清晰速度也更快。看State这一列如果是LISTEN说明服务在听是SYN-SENT说明发出的连接请求没人响应是ESTABLISHED说明已经建立了连接。端口不通的情况除了服务本身没起来最大嫌疑就是防火墙。检查顺序是这样的# 查看防火墙规则firewalld firewall-cmd --list-all # 查看iptables规则 iptables -L -n -v # 临时放行某个端口测试 iptables -I INPUT -p tcp --dport 3306 -j ACCEPT一套查完基本能区分是防火墙拦截还是服务本身的问题。还有一点需要注意云服务器上还有一个安全组的概念它是在虚拟机外面那一层拦的服务器内部的防火墙查不出问题时要想到去云控制台看安全组规则。5.3 DNS问题的隐蔽性能ping通IP却打不开网站“能ping通但浏览器打不开网页”这个问题在运维工单里出现的频率极高。ping用的是ICMP协议而访问网站用的是TCP协议和DNS解析。能ping通只能说明网络层通和DNS、端口是否开放没有关系。排查DNS问题先确认解析是否正常# 使用系统配置的DNS做解析 nslookup www.example.com # 手动指定一个公共DNS做对比判断是不是系统DNS配置的问题 nslookup www.example.com 223.5.5.5 # 直接看系统DNS配置 cat /etc/resolv.conf如果指定了公共DNS能解析但用系统DNS解析失败那就是/etc/resolv.conf配置有问题。常见的坑包括DNS服务器地址配置错误或者不可达。resolv.conf里的search域配置导致解析了不必要的域名增加了查询时间。systemd-resolved或NetworkManager可能动态重写这个文件手工改完一重启又被覆盖。DNS超时时间可能很短导致解析反复失败。网络排查的最终心法就是每一层都要有“确定通”的证据才能进入下一层。底层没通就跑到应用层去翻日志是浪费时间底层全通还怀疑网线也是浪费时间。按照顺序检查即使问题不能马上解决也能明确缩小到具体是哪一层后续求助或者提单都有明确的方向。6. 日志系统和systemd运维手里最锋利的“黑匣子”如果让我选一个“如果只能教运维新手一个技能我会选什么”那一定是“会看日志”。Linux系统里几乎所有的组件都会产生日志只是输出的位置和格式各不相同。掌握日志的查看方法就等于拿到了系统的“黑匣子”。6.1 journalctl的正确打开方式现在主流的Linux发行版都使用systemd作为init系统配套的日志系统是journald。很多人只知道journalctl能看日志但没有用好它的“时间窗口”“单元过滤”“优先级过滤”这几个核心能力。实战中我经常要回答一个问题“昨晚2点到3点之间发生了什么”这时候直接用# 查看某个时间段的全部日志 journalctl --since 2025-01-15 02:00 --until 2025-01-15 03:00这个时间窗口能力在做故障复盘时尤其重要。还有一个很有用的场景排查“某个服务为什么启动失败”。# 查看服务最近一次启动的完整日志不截断 journalctl -u nginx.service -b --no-pager # 查看该服务的最近日志并持续跟踪 journalctl -u nginx.service -f # 查看服务上次启动失败的完整输出 journalctl -u nginx.service -b -1 --no-pager很多服务启动失败的真正原因在systemctl status输出的最后几行里根本看不出来必须用journalctl看完整日志。比如我在排查PHP-FPM启动失败时systemctl status只显示failed但journal里明确写着“address already in use”——端口被占用了。这个差别决定了排查时间是三分钟还是三小时。6.2 日志轮转让磁盘不被日志写满的“保命配置”日志文件永远只增不减如果不做轮转rotation任何系统最终都会磁盘满。绝大多数发行版默认都装了logrotate但默认配置不一定适合所有业务所以自己要学会配。一个生产环境常见的配置长这样cat /etc/logrotate.d/myapp /var/log/myapp/*.log { daily rotate 14 compress delaycompress missingok notifempty dateext postrotate /bin/kill -USR1 $(cat /var/run/myapp.pid 2/dev/null) 2/dev/null || true endscript }配置项的意思分别是每天轮转一次保留14份旧日志压缩延迟一天压缩避免刚轮转就压缩影响还在写的平滑过渡日志文件不存在不报错空文件不轮转日志文件名带日期后缀。关键是最后那一段postrotate——轮转之后向应用进程发送信号让它重新打开日志文件否则应用还握着旧文件的句柄日志会继续写到被改名但还没删除的文件里又会出现“空间不释放”的老问题。不同应用重新加载日志的信号不同Nginxkill -USR1 nginx-master-pidApachekill -USR1 apache-pidJava应用Logback通常不需要信号它会自己感知文件变化。6.3 服务起不来的通用检查路径无论是自己写的脚本还是标准的系统服务启动失败都遵循一个共通排查路径# 第一步查看服务状态确认失败原因 systemctl status my-service # 第二步查看该服务的完整journal日志 journalctl -u my-service -b --no-pager | tail -100 # 第三步检查服务的依赖项是否正常 systemctl list-dependencies my-service # 第四步手工运行一次直接看终端输出很多服务的报错不会写全到journal sudo -u 运行用户 /usr/local/bin/my-service --foreground第四步非常管用因为它绕过了systemd直接把服务的标准输出打到终端上任何报错信息都逃不掉。但要记住一个原则手工运行前先确认不会对现有环境造成影响。比如一个端口型服务手工前台运行可能和你正在跑的实例冲突那就先停掉systemd管理的实例或者换个测试端口再跑。7. Linux系统运维故障排查QA高频疑问解答篇幅有限没法把191页手册里的所有内容都放进一篇文章。但有几个高频问题几乎每个运维都被问过我在这一节集中解答掉。7.1 系统负载突然飙高但找不到是哪个进程在搞事出现这种情况十有八九是以下三类原因短时间内的进程高峰已经过去但load平均值还没降下来。这时候看uptime里的1分钟、5分钟、15分钟数值变化趋势如果1分钟已经明显回落那基本就是瞬时高峰。进程在D状态不可中断睡眠通常卡在磁盘IO。ps aux里看到大量D状态的进程用iostat -x 1确认磁盘是否异常。容器环境下宿主机上其他容器的资源竞争。单独看容器内的top是看不到宿主整体情况的要在宿主机上用top观察整体。7.2 Linux系统安装了软件但找不到命令刚部署完Linux环境时systemctl或者nginx命令提示“command not found”通常不是因为软件没装上而是PATH环境变量没包含对应目录。排查思路如下# 确认命令是否存在只是不在PATH里 find / -name nginx -type f 2/dev/null # 查到实际路径后尝试直接执行 /usr/local/nginx/sbin/nginx -v # 如果要长期可用把路径加入PATH export PATH$PATH:/usr/local/nginx/sbin7.3 运维常用的“后悔药”备份和回滚最后说一个理念可能比任何具体命令都重要一切变更之前必须考虑回滚方案。改配置前备份原文件动数据库前做备份换内核前保留旧内核。这些看似多花两分钟的动作在故障发生时就是你的“后悔药”。# 改配置前备份 cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date %Y%m%d%H%M) # 做变更之后验证确认没问题再真正收工 nginx -t systemctl reload nginx # 升级内核前确认旧内核还在防止新内核无法启动时束手无策 grubby --infoALL | grep ^kernel8. 写在最后运维故障排查的核心心法说了这么多具体的命令和场景最后想分享一下这几年来我个人在故障排查这件事上的体会。第一故障排查拼的不是记忆力是方法论。那些看起来“他好厉害一秒就定位了”的同行不是背下了所有报错的含义而是脑子里有一套清晰的排查框架。无论是启动、CPU、磁盘还是网络问题有框架的人在按步骤缩小范围没框架的人在到处试运气。第二要把每一次故障当作一次学习机会。我建议每个运维都建立一个自己的“故障档案”记录日期、现象、影响范围、排查过程、根因、解决方案、后续预防措施。这个档案刚开始可能没什么感觉但积累半年到一年后你会发现自己对系统的理解会有质的飞跃。很多看似“新”的故障其实都能从档案里找到类似的影子。第三敬畏线上环境重视变更管理。大部分故障都来源于变更。“能用一条命令解决的问题绝不用两条能不在高峰期做的操作绝不在高峰期做能不重启的尽量热加载非要重启的一定提前确认所有依赖。”这几句话听起来平平无奇却是无数个凌晨四点的故障电话换来的教训。第四善于利用工具但没有万能钥匙。这本文档里提到的top、dmesg、journalctl、iostat、perf、lsof都是好工具但它们只是放大器——放大你大脑里的排查思路不能替代思路本身。真正值钱的是你能在正确的时候选择正确的命令并且读懂它输出背后的含义。Linux系统运维这个岗位看起来是和冰冷的机器打交道但本质上是在和“不确定性”打交道。系统总是会以你意想不到的方式出问题你能做的不是杜绝问题而是在问题来临时有章法、不慌乱、快速恢复并且从每一次故障中让系统变得更健壮。希望这篇文章的分享能帮你在下一次遇到问题时少走一些弯路多一份从容。
返回列表