df与du为何不一致:在Linux上查找隐藏的千兆字节
df -h命令显示磁盘使用率为95%,而du -sh /*仅汇总出20%。剩余的75%对du不可见,却占用了磁盘块。原因是进程持有的打开文件描述符指向已删除的文件。df直接读取文件系统超级块,du遍历目录树并忽略其中没有条目的文件。
inode存储文件元数据,包括数据块列表。rm仅移除目录条目(硬链接),inode链接计数下降,但如果进程持有描述符,块仍保持分配状态。
图示:
删除前:
目录 inode 数据块
/var/log/ --> app.log --> [块1][块2]
^
nginx描述符 (pid 1234, fd 7)
删除后:
目录 inode 数据块
/var/log/ --> (已删除) --> [块1][块2]
^
nginx描述符
du在/var/log/中看不到该文件,df记录占用的块。
问题的典型场景
日志轮转:logrotate对旧文件执行rm,但nginx或java持有启动时的描述符。进程写入已删除的文件,df记录使用量增加,du则不会。
其他场景:
- 长期运行的守护进程打开日志文件。
- 清理脚本中的错误,其中rm先于描述符关闭。
- Docker容器中内部进程持有主机文件。
问题会累积:千兆字节在不知不觉中消失,直到磁盘填满。
查找持有已删除文件的进程
使用lsof +L1查找NLINK < 1的文件(从文件系统删除但打开):
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
nginx 1234 www 7w REG 8,1 2147483648 0 12345 /var/log/app.log (deleted)
java 5678 app 3w REG 8,1 536870912 0 67890 /var/log/service.log (deleted)
关键字段:
- COMMAND/PID:进程和ID。
- FD:描述符(7w — 写入,编号7)。
- SIZE/OFF:大小 — 丢失GB的来源。
- NLINK=0:删除确认。
- NAME (deleted):rm前的路径。
大小过滤:lsof +L1 | awk '$7 > 104857600 {print}'(文件>100MB)。
无需重启释放空间
对于进程PID 1234,FD 7:
$ > /proc/1234/fd/7
/proc是内存中的procfs。该命令通过描述符将文件清零,内核立即释放块。df更新,进程继续写入fd 7。
检查:lsof +L1 | grep 1234 — SIZE/OFF=0或为空。
脚本自动化:
#!/bin/bash
lsof +L1 | awk '$7 > 104857600 && $9 ~ /deleted/ {print $2, $8}' | while read pid fd; do
echo "清零 $pid/$fd"
> /proc/$pid/fd/$fd
echo "已释放"
done
在logrotate中预防问题
避免在轮转中使用rm。在/etc/logrotate.d/app中:
/var/log/app.log {
daily
rotate 7
compress
copytruncate
}
copytruncate复制日志,然后截断原始文件(>机制)。描述符不变。
缺点:复制时的微延迟 — 可能丢失行。
零丢失替代方案:
- logrotate:mv旧文件 → 新文件。
- kill -HUP <pid>(SIGHUP用于重新打开日志)。
nginx:nginx -s reopen。systemd:systemctl reload nginx。
监控与告警
检查脚本:
lsof +L1 -F s | awk '/^s/ && substr($0,2)+0 > 104857600 {count++} END {print count+0}'
输出>100MB的已删除文件计数。
Prometheus集成:
node_filefd_allocated— 打开fd的增长。- 比较df/disk_used与du总和。
每5分钟cron:如果差异>10%则告警。
关键要点
- df显示超级块中的分配块,du显示目录中可见的文件。
lsof +L1通过NLINK=0查找持有(已删除)文件的进程。- 释放空间:
> /proc/PID/fd/FD— 无需重启清零。 - logrotate copytruncate无需HUP即可预防问题。
- 监控:使用lsof或node_exporter指标的脚本。
— Editorial Team
暂无评论。