场景概述
现象很好认:df 显示某挂载点持续接近 100%,du 从根目录逐层统计却找不到对应的大文件,同时应用仍在正常运行、日志仍在写入。原因是文件删除只摘掉了目录项,进程手里的文件描述符仍指向原来的 inode,只有最后一个句柄关闭,数据块才会被回收。这类占用在 du 的视野之外,却在 df 的统计之内,是容量排查中最典型的「看不见的占用」。
场景信息
| 触发条件 | rm 之后 df 没有下降,空间告警持续存在 |
|---|---|
| 排查目标 | 找出被持有的已删除文件与持有进程,并用最小代价释放空间 |
| 工具链 | df 确认差异 → lsof +L1 列出已删除文件 → /proc/PID/fd 交叉验证 → 重载或重启进程 |
| 判据 | df 已用明显大于 du 合计,且 df 仍在上涨,通常是日志类文件被删除但进程未重开 |
常见来源
- 日志被误删:logrotate 配置错误或人工 rm 掉了正在写的日志文件,应用句柄仍然有效
- 临时导出文件:备份与导出任务产生的大文件被删除,但任务进程还没退出
- 容器场景:容器内删除文件后宿主机的 overlay 层仍持有,容器运行时日志文件被删同理
- 数据库与中间件:WAL、binlog 或临时段文件被清理但进程未释放句柄
- 脚本误判:清理脚本按名字删除后又重新创建同名文件,旧句柄继续写入旧 inode
排查与处置命令
排查与处置命令
# 第 1 步:列出 link count 为 0 的已删除文件,按大小排序
lsof +L1 2>/dev/null | sort -k7 -n | tail -n 20
lsof -nP +L1 | awk '$7>1073741824 {print}' # 只关注大于 1GiB 的
# 第 2 步:交叉验证持有进程与句柄
ls -l /proc/<PID>/fd | grep deleted
ls -l /proc/<PID>/fdinfo/<FD>
# 第 3 步:处置——首选让应用重开文件
systemctl reload <服务> # 支持平滑重载的优先用 reload
systemctl restart <服务> # 不支持时重启,提前确认业务影响
kill -HUP <PID> # 很多服务收到 HUP 会重开日志文件
# 第 4 步:确认空间释放
df -h; lsof +L1 | head判据与处置对照
| 现象 | 结论 | 处置 |
|---|---|---|
| df 满而 du 找不到大文件 | 存在被持有的已删除文件 | 用 lsof +L1 确认 |
| lsof +L1 列出 (deleted) 条目 | 确认持有进程与文件大小 | 按业务重要性选择重载或重启 |
| /proc/PID/fd 中同一天有 deleted 条目 | 该进程正在写已删除文件 | 重开日志文件或重启进程 |
| 重启后空间仍不下降 | 还有别的进程持有,或占用来自容器分层 | 扩大排查范围,检查容器与宿主层 |
lsof 的 +L1 表示「链接数小于 1」,即文件已没有目录项指向它。全量 lsof 输出可能几十万行,先用 +L1 收窄再按 SIZE 列排序,能直接看到最占空间的那几个句柄;也可以在 /proc/*/fd 上按 deleted 关键字扫描,速度更快但需要自己解析。
让应用重新打开日志文件是首选处置:支持信号的服务收到 HUP 会重开;有 ExecReload 的单元用 systemctl reload;两者都不支持再考虑 restart。对数据库这类进程,重启前必须确认复制状态与业务低峰,硬重启带来的恢复时间往往比多做几步确认更长。
「把这个文件再 rm 一次」对 deleted 句柄无效,目录项已经不存在。另外,若应用持有的句柄没有用追加方式打开,重建同名文件并截断后,旧句柄仍会按原偏移写入,产生一个占用巨大但对不上实际内容的空洞文件,这类文件在 du 与 ls -l 的读数上也会明显不一致。