df 说磁盘满了,du 却找不到大文件:一次 Linux 磁盘"幽灵占用"排查实录
排查 Linux 服务器磁盘问题时,最让人困惑的场面莫过于:df -h 显示分区 100%,但 du 把所有目录加起来,数字远小于分区容量。空间像被"幽灵"吃掉了。
这篇文章给出一套完整的排查路径。先说结论:df 和 du 的口径本来就不一样——df 统计的是文件系统块层已分配的空间,du 统计的是还能从目录树索引到的文件。两者差值大,说明空间被"目录树之外的句柄"占着。
一、第一嫌疑:已删除但仍被占用的文件
这是最高频的原因。典型场景:某个进程(日志服务、应用自己写的守护脚本)一直在写一个日志文件,运维用 rm 把文件删了,但进程还握着这个文件的句柄继续写。
rm 删的是目录项(文件名到 inode 的链接),只要有进程还打开着这个 inode,文件数据就仍然占着磁盘空间——而 du 顺着目录树已经找不到它了。
正确的排查命令不是 lsof | grep deleted(大机器上输出多且容易漏),而是按"链接数为 0 但仍被打开"精确过滤:
lsof +L1
输出里带 (deleted) 标记的大文件就是元凶。处理分两种情况:
情况一,进程可以重启:直接重启对应服务,句柄释放,空间立刻归还。
情况二,进程不能重启(比如生产环境的日志进程):不要 rm,用截断的方式释放内容:
> /proc/进程PID/fd/文件描述符号
注意是用重定向清空内容,不是删掉 /proc 下的链接。清空后 df 立刻恢复正常,进程继续正常写入。
二、lsof 不可用时,直接扫 /proc
最小化环境里可能没有 lsof,用 shell 遍历所有进程的 fd 目录同样有效:
for pid in /proc/[0-9]*; do ls -l $pid/fd 2>/dev/null | grep deleted; done
输出里同样能看到 (deleted) 文件和对应进程。
三、第二个嫌疑:挂载点遮蔽
现象:du 的结果和 df 差距巨大,且 lsof +L1 毫无发现。
成因:在某个大目录上又挂载了新磁盘。比如 /var 已经堆积了 80GB 旧数据,后来在 /var 上挂了新盘,旧数据被新文件系统"盖住"了——du 看到的是新盘的内容,旧数据仍然占着底层分区的空间。
验证方法是用 bind mount 绕过子挂载点看底层:
mkdir /mnt/root
mount --bind / /mnt/root
du -sh /mnt/root/var
bind 挂载根目录本身不包含子挂载点,看到的就是被遮蔽的旧数据。确认后 umount /mnt/root,再决定旧数据怎么清理。
四、三个次常见原因
1. 文件系统预留块。ext4 默认给 root 用户预留 5% 的空间,小盘上这 5% 很可观。查看:tune2fs -l /dev/vda1 | grep -i reserved。生产盘可以考虑用 tune2fs -m 1 把预留比例降到 1%。
2. inode 耗尽。df -h 显示有空间,但写入报 No space left。用 df -i 检查 IUse%,100% 时删除大量小文件(邮件队列、session 文件、缓存碎片是重灾区)。
3. 稀疏文件。对比 du -h 和 du -h --apparent-size,后者按逻辑大小统计。数据库裸设备、虚拟机磁盘镜像常是稀疏文件,du 的口径更接近真实占用量。
五、根治:让这类问题不再发生
排查是治标,三个习惯治本:
1. 日志统一接 logrotate,用 copytruncate 模式或 postrotate 钩子里 reload 进程,从机制上杜绝"删了文件进程还在写"。
2. 挂载新盘前先检查目标目录是否已有大量数据,有则先迁移再挂载,避免遮蔽。
3. 部署基础的磁盘监控(任何监控体系都有现成的 node_exporter 文件系统指标),对 df 口径的使用率做告警,别等 100% 才上服务器救火。
六、排查清单(存下来下次直接用)
第一步 lsof +L1 找幽灵文件;
第二步扫描 /proc 找 deleted 句柄;
第三步 bind mount 检查挂载点遮蔽;
第四步查预留块、inode、稀疏文件;
第五步上监控和日志轮转,根治复发。
按经验,十例里七八例倒在第一步:lsof +L1 一跑,那个隐形占用者就现形了。如果你正被这个问题困住,现在就开个终端试第一条命令。
- 点赞
- 收藏
- 关注作者
评论(0)