df 和 du 为什么给出不同的答案
一句话总结
df 向文件系统询问,du 则遍历目录后计数。因此,没有名称的文件会被 df 统计,却不会被 du 统计。
为什么需要了解这一点
日志占满磁盘后,用 rm 删除了大文件,df 显示的可用空间却连 1 字节都没有增加。几乎每个人都经历过,也都曾因此困惑。
如果此时得出“rm 没有效果”的结论,这一夜就会变得漫长。实际发生的是:rm 不是删除文件的命令,而是从目录中移除一个名称(unlink)的命令。要归还数据 block,必须同时满足两个条件。
- 不再有任何名称指向该实体
- 也没有任何进程保持该实体打开
如果写入日志的应用仍然打开着该文件,第二个条件就不成立。名称虽然消失,数据仍然存活;du 通过遍历路径计数,因此永远找不到这个没有名称的文件。df 与 du 出现差异的瞬间,正是这种情况的信号。
工作原理
缩小容量问题范围的顺序如下。
df -h # 블록 사용량
df -i # inode 사용량 (이게 100% 일 수도 있다)
du -x -h --max-depth=1 / | sort -h # 어느 디렉터리가 큰가 (-x: 다른 fs 로 안 넘어감)
lsof +L1 # 삭제됐지만 열려 있는 파일
ls -l /proc/<PID>/fd | grep deleted
find / -xdev -size +500M -printf '%s %p\n' | sort -rn | head
每个工具回答的问题都不同。
| 工具 | 问题 | 会漏掉什么 |
|---|---|---|
df |
文件系统还剩多少空间 | 不知道空间被哪里占用 |
df -i |
还剩多少 inode | block 使用量 |
du |
这条路径下有多大 | 无名称文件、其他文件系统 |
lsof +L1 |
是否存在已删除但仍打开的文件 | 已经关闭的文件 |
du 还有两个陷阱。
- 硬链接只计算一次。 多个名称指向同一个 inode 时,du 只计算最先遇到的一个。因此在测量 generation backup 目录大小时,结果会比预期小。
- 稀疏文件只计算实际占用量。 添加
--apparent-size后会显示逻辑大小,两者之差就是 sparse area。
恢复已删除但仍打开的文件
只要进程仍然打开该文件,/proc/PID/fd 下就会保留通向该文件的 symlink。
ls -l /proc/1234/fd | grep deleted
cp /proc/1234/fd/7 /backup/recovered.log
这里顺序最为重要。 重启进程的瞬间,最后一个引用会消失,block 被归还,恢复机会也会彻底失去。即使空间告急,第一个动作也应是复制,而不是重启。
如果只需紧急回收空间,可以通过 > /proc/1234/fd/7 清空文件。对于日志文件,这种方法可以在保持进程运行的同时归还空间。
快速缩小空间占用范围
容量问题是一场与时间的竞赛。与其遍历全部内容,不如从顶层开始,每次向下缩小一半, 通常三四次就能找出原因。
du -xh --max-depth=1 / 2>/dev/null | sort -rh | head
-x 表示不要进入其他文件系统。缺少它,就会遍历 /proc、
/sys 和 network mount,不仅耗时很长,数字也会错误。使用 --max-depth=1
逐层深入,不断 cd 到最大的目录。
先判断是几个大文件,还是数百万个小文件。 两者的处理方法不同。
find /var -xdev -type f -size +100M -printf '%s\t%p\n' | sort -rn | head
find /var -xdev -type f | wc -l
如果前一条命令给出答案,删除或移动相应文件即可。如果后一项达到数百万, 问题就在 inode 和目录遍历,连删除本身都会耗时很长。
从旧文件开始删除。 如果给 rm 传入过多参数,会出现 Argument list too long,
因此应使用 -delete 或 xargs。
find /var/log/app -xdev -type f -name '*.log' -mtime +14 -delete
find /var/cache/thumbs -xdev -type f -mtime +30 -print0 | xargs -0 -r rm -f
删除前先查看谁正在写入。 删除某个进程仍在写入的文件后,空间不会归还, 只会让该进程变得异常(前一模块中的“已删除但仍打开的文件”)。
lsof -nP +D /var/log/app 2>/dev/null | head
防止同一问题再次发生。 只清理一次就结束,磁盘一定会再次填满。
日志应交给 logrotate(copytruncate 是处理 open fd 的安全措施);
如果是 container host,应使用 docker system df 分别查看 image、volume 与 build cache,
再用 --filter until= 指定时间范围进行清理。与其在 90% 时发出 alert,不如按增长速度
告警。如果每天增长 5%,就能知道还剩几天,并且不必在凌晨被叫醒。
实际工作中的表现
inode 耗尽。 session 文件或 mail queue 创建数百万个小文件时,即使 block 还有剩余,inode 也会先耗尽。不查看 df -i 就无法找到原因。而且 ext4 格式化后不能增加 inode,根本解决方式是重新格式化,或把该 workload 移到其他文件系统(XFS 会动态分配)。
du 耗时太久,延误故障处理。 文件达到数百万个时,du 会运行数分钟。紧急情况下,使用 du --max-depth=1 从顶层逐步缩小范围更快。
下个练习将做什么
批量创建小文件以观察 inode 消耗,比较稀疏文件的两种大小,并亲自创建一个已删除但仍打开的文件,复现 df 与 du 不一致的现象。