Linux磁盘与IO排查:四类故障的定位路径
Linux系统 2026-11-08 20:43 4

df -h 显示 100%,du 加起来只有一半;df 明明还有空间,程序却报 No space left on device;磁盘没满但 IO 100%,数据库拖到卡死——这三个是 Linux 上最常见的存储故障,判断路径完全不同,处理错方向会浪费大量时间。

这篇按「先分清是空间问题还是 IO 问题」入手,给出一套从 df 到 iostat 的排查路径,每个症状对应具体命令和典型原因。所有命令都是运维日常在用的,可以直接复制。

第一步:分清三类故障

症状最可能原因第一条命令
df 显示 100%真的有文件占满了du -sh /* 2>/dev/null | sort -h
df 显示 100%,du 加起来对不上已删除但被进程占用的文件(deleted)lsof +L1
df 有空间但报 No space leftinode 用尽df -i
空间正常但 IO 打满大文件读写、日志刷盘、数据库刷脏页iostat -x 1

先花 10 秒跑上面四条命令,基本就能定位到大方向。别一上来就 rm -rf,删错文件比磁盘满更麻烦。

场景一:df 满了,du 也算得出来

按体积从大到小逐层排查,别直接扫全盘(大目录很慢):

  • du -sh /* 2>/dev/null | sort -h | tail -10:找到最大的顶层目录
  • 再对那个目录重复同样命令,两三轮就能找到具体位置
  • find /path -type f -size +500M -exec ls -lh {} \;:直接找大文件
  • find /path -type f -mtime -1 -size +100M:找最近一天新增的大文件,日志暴增常用这招

最常见的五个占空间大户:

  • 日志:/var/log 下的 journal、nginx access.log、应用日志。journal 用 journalctl --vacuum-size=500M 收缩。
  • MySQL binlog:/var/lib/mysql 下 binlog 能涨到几十 G。用 PURGE BINARY LOGS BEFORE ... 清理,并设置 expire_logs_days。
  • Docker:docker system df 看镜像、容器、卷各占多少;docker system prune -a 清无用的。日志没设轮转的容器能写出几十 G。
  • 备份文件:本地留了七份全量备份,是最常见的自作自受。
  • 临时文件与缓存:/tmp、包管理器缓存(apt clean / yum clean all)、CDN 或反代的缓存目录。

场景二:df 满了,du 对不上

这是新手最容易懵的情况。原因:文件被删除,但仍有进程持有文件句柄,空间不会释放。

  • 查:lsof +L1 或 lsof | grep deleted,看哪个进程占着已删除的文件
  • 处理:重启对应进程(推荐),或者 truncate -s 0 /proc/PID/fd/FD(不重启但会丢该进程后续写入的偏移,谨慎)

典型场景:日志文件被 rm 掉了,但 nginx 还开着句柄,磁盘一直显示满。正确做法是用 logrotate 或 > file.log 清空,而不是 rm。

另一种对不上的情况:目录被挂载覆盖了。mount | grep 看是不是下面还挂了一个分区,导致 du 统计的是被覆盖前的空目录。

场景三:空间够但报 No space left

九成是 inode 用尽:小文件太多,每个文件占一个 inode。

  • 查:df -i,看 IUse% 是否接近 100%
  • 定位:for i in /*; do echo -n "$i "; find $i | wc -l; done,找出小文件最多的目录
  • 典型元凶:session 文件(/var/lib/php/sessions)、邮件队列(/var/spool/postfix)、大量日志分片、缓存碎片

处理:清理无用小文件;长期方案是把这类目录单独挂一个分区,或者用 tmpfs/Redis 替代基于文件的 session。

场景四:IO 打满,但空间正常

先用 iostat -x 1 看是读还是写、是随机还是顺序。关键指标解读:

指标含义异常判断
%util设备繁忙度持续 90% 以上说明饱和
await平均 IO 等待(ms)SSD 超过 10ms、机械盘超过 30ms 就该查
r/s w/s每秒读写次数突发很高说明有大量小 IO
rkB/s wkB/s吞吐与实际业务量对比,不对等说明有后台任务
aqu-sz请求队列长度持续大于 2 说明排队

再定位到进程:

  • iotop -oPa:按累计 IO 排序,直接看哪个进程在读写
  • pidstat -d 1:看每个进程的读写速率
  • cat /proc/PID/io:看单进程的 read_bytes / write_bytes

常见的 IO 来源按频率排:数据库刷脏页(innodb_flush_log_at_trx_commit=1 时每次提交都刷盘)、备份任务、日志同步写、大文件传输、容器层复制(overlay 的写放大)。

第五步:按原因处置,别只做清理

清完一次很快就再满,说明没解决根因。对每类问题给一个长期方案:

  • 日志:配 logrotate(按天轮转、保留 7–14 份、压缩),或给 Docker 配 max-size。应用日志分级,别把 debug 全打开。
  • 数据库:binlog 设过期时间、慢查询日志限大小、临时表目录单独挂盘。
  • 备份:本地只留最近 2–3 份,其余推异地。备份目录单独分区,别和系统盘抢空间。
  • 监控:磁盘使用率告警设在 80%,inode 使用率也设一条。IO 等待(iowait)持续超 20% 也报警。

扩容:什么时候加盘,什么时候换盘

两种情况要分开判断:

  • 空间不够:加盘或扩云盘。扩云盘后还要扩文件系统(xfs_growfs 或 resize2fs),很多人只扩了盘没扩文件系统,df 还是旧容量。
  • IO 性能不够:加盘解决不了。要看是不是随机 IO 太多、队列太深。换 NVMe、加内存(扩大文件缓存与数据库缓冲池)、把高 IO 目录拆到独立盘,都比加容量有效。

一个经验判断:%util 高但吞吐低,是随机小 IO 瓶颈,靠换更快的介质;%util 高且吞吐接近设备上限,是顺序大 IO,靠加带宽或分盘。

常见问题

Q:df 显示有空间,程序还是报写入失败?
按顺序查三处:df -i 看 inode、lsof +L1 看 deleted 文件、mount | grep ro 看是不是被挂成只读了(磁盘故障后内核会自动转只读)。

Q:删了大文件,df 没变化?
文件还被进程持有。用 lsof +L1 找到进程,重启它。这也是「删了日志但磁盘没释放」的标准答案。

Q:iowait 高一定是磁盘问题吗?
不一定。NFS/网络存储慢、swap 交换也会表现为 iowait 高。先 free -h 看 swap 是否在用,再 vmstat 1 看 si/so。

Q:能不能直接删 /var/log 下的东西?
能删已轮转的 .gz 和旧文件,别删正在写的当前日志(会导致句柄悬空)。宁可 truncate -s 0 清空,也别 rm。

Q:怎么提前发现盘要满?
三个告警:使用率 80%、inode 使用率 80%、每日增长量异常(比如比上周同日涨了 3 倍)。第三个最有价值,能在满之前发现「有东西在偷偷长大」。

小结

记住这条判断链:先 df -h 看空间,再 df -i 看 inode,空间和 inode 都正常就跑 iostat -x 1 看 IO,最后用 iotop 落到进程。四条命令覆盖 95% 的存储故障。

比排查更重要的是「不让它发生」:日志轮转、备份分离、binlog 过期、监控三个告警。这四件事做完,你基本不会再在半夜被磁盘满叫醒。

相关阅读:Docker 上生产:16 项设置、LNMP 调优清单、服务器性能自测脚本合集、服务器迁移不丢数据的完整清单。

本站深度稿系列:从0搭一个扛得住流量的站:LNMP调优清单、Docker上生产:从镜像到升级的16项设置、Windows服务器加固20项:从账号到离线备份。

Powered by ©智简魔方