实战排查 Linux 生产环境磁盘空间占满但 du 查不到大文件(被删文件句柄未释放)深度剖析与线上无损恢复

举报
yd_265305575 发表于 2026/11/06 20:50:13 2026/11/06
【摘要】 实战排查 Linux 生产环境磁盘空间占满但 du 查不到大文件(被删文件句柄未释放)深度剖析与线上无损恢复 1. 问题背景在 Linux 生产服务器的日常运维中,磁盘告警是最常见的故障之一。某天核心业务系统的监控告警系统突然弹出高危预警:某节点 /data 分区使用率达到 98%,可用空间不足 2GB。按照常规排查流程,运维人员立刻登录服务器执行 df -h 确认磁盘使用情况:[root...

实战排查 Linux 生产环境磁盘空间占满但 du 查不到大文件(被删文件句柄未释放)深度剖析与线上无损恢复

1. 问题背景

在 Linux 生产服务器的日常运维中,磁盘告警是最常见的故障之一。某天核心业务系统的监控告警系统突然弹出高危预警:某节点 /data 分区使用率达到 98%,可用空间不足 2GB。

按照常规排查流程,运维人员立刻登录服务器执行 df -h 确认磁盘使用情况:

[root@prod-node-01 ~]# df -h /data
Filesystem      Size  Used Avail Use% Mounted on
/dev/vdb1       200G  196G  4.0G  98% /data

确认分区确实几乎打满。然而当使用 du 命令递归扫描该挂载目录下的所有文件占用时,诡异的一幕出现了:

[root@prod-node-01 ~]# du -sh /data/* | sort -hr | head -n 10
12G    /data/app-services
8.5G   /data/mysql-data
1.2G   /data/nginx-logs

所有子目录与可见文件的总占用加起来不足 25GB,而 df 显示已使用 196GB。剩余的约 170GB 磁盘空间神秘“蒸发”了。


2. 根因剖析:为什么 df 与 du 的统计结果不一致?

要理解这一现象,必须从 Linux 虚拟文件系统(VFS)的底层存储机制说起。

在 Linux 文件系统中,一个文件的存储由两部分构成:

  1. inode(索引节点):存储文件的元数据信息,包括文件属主、权限、创建时间、文件大小以及指向数据块(data block)的指针列表。同时 inode 内部维护着一个硬链接计数器 i_nlink。
  2. dentry(目录项):包含文件名与对应 inode 编号的映射关系。

当我们执行 rm -f /data/logfile.log 时,内核执行的实质是系统调用 unlink()。该调用仅仅是从父目录的 dentry 树中移除了该文件名,并将对应 inode 的 i_nlink 计数减 1。

关键在于:磁盘空间的真正回收,必须同时满足两个条件:

  1. inode 的硬链接计数 i_nlink == 0;
  2. 引用该 inode 的进程打开句柄数(File Descriptor Open Reference Count)i_count == 0。

如果某个长期运行的后台进程(如 Nginx、Java 应用、Python 脚本或 logstash)在文件被删除前已经打开了该文件,且之后一直没有关闭句柄,那么即使文件名被移除了,该文件的 inode 与数据块依然牢牢驻留在磁盘上。

  • du 命令的统计逻辑:从指定目录树向下递归遍历所有可见的 dentry,将找到的每个文件大小累加。因为被删文件的目录项已经消失,du 无法扫描到它,因此统计出的占用偏小。
  • df 命令的统计逻辑:直接读取文件系统超级块(Superblock)中记录的空闲块(Free Blocks)与总块数,反映的是磁盘底层的真实分配情况。只要句柄未释放,数据块依然被占用,df 就会显示磁盘打满。

3. 定位罪魁祸首:快速揪出持有句柄的进程

面对这种情况,切忌盲目扩容或胡乱清理其他业务数据。我们需要使用 lsof(List Open Files)命令来精准定位持有已删除文件句柄的进程。

3.1 查找特定挂载点下的已删除未释放文件

# 语法:lsof +L1 <挂载点路径>
# +L1 表示只列出硬链接数小于 1(即已被 unlink 删除)的文件
lsof +L1 /data

或者在全系统范围内排查并按占用空间从大到小排序:

lsof | grep '(deleted)' | sort -k7 -nr | head -n 10

输出示例:

COMMAND     PID   USER   FD   TYPE DEVICE   SIZE/OFF   NODE NAME
java     104852   root   42w   REG  252,17 182536110080 393220 /data/app/logs/catalina.out (deleted)

从输出结果可以清晰看到:

  • COMMAND / PID:java 进程,进程 ID 为 104852。
  • FD:42w,表示文件描述符为 42,以写入模式打开。
  • SIZE/OFF:占用空间约为 182GB!
  • NAME:/data/app/logs/catalina.out (deleted),已被删除但句柄未关闭。

4. 线上优雅治理:无损在线释放空间

找到占用源后,常规但粗暴的做法是直接重启或强杀业务进程(kill -9 104852),这样内核在回收进程资源时会自动关闭其持有的所有句柄,空间即可瞬间释放。

但在核心生产环境中,重启可能导致正在执行的事务中断、长连接断开或服务抖动。如何在不重启、不中断进程的前提下在线释放空间?

4.1 方案:利用 /proc 伪文件系统进行在线截断(推荐)

Linux 的 /proc/<PID>/fd/ 目录下暴露了该进程打开的所有文件描述符的软链接。即使磁盘上的原始文件名已删除,内核依然允许通过 /proc/<PID>/fd/<FD> 访问并操作对应的数据块。

我们可以通过重定向清空该描述符:

# 检查该文件描述符确实对应目标文件
ls -l /proc/104852/fd/42

# 使用空内容截断(清空)该文件
: > /proc/104852/fd/42
# 或者
cat /dev/null > /proc/104852/fd/42

执行后立即复查磁盘:

df -h /data

此时会发现已用空间瞬间从 196GB 降回 14GB,可用空间恢复正常,且 Java 业务进程完全没有中断,依然正常运行并继续向文件追加新写入的日志。


5. 生产避坑与长效防范机制

线上应急解决后,必须建立制度与自动化配置,从根本上杜绝该问题再次发生:

5.1 正确的日志清理与轮转规范

  1. 严禁直接 rm 正在写入的活跃日志:
    对于运行中进程正在写入的大日志文件,若急需释放空间,严禁直接执行 rm file.log,而应执行截断:
    > /data/logs/service.log
    
  2. 规范配置 logrotate:
    在配置日志切分时,若应用程序不支持通过信号重新打开日志文件,必须在 logrotate 配置中声明 copytruncate 指令:
    /data/app/logs/*.log {
        daily
        rotate 7
        missingok
        notifempty
        compress
        copytruncate
        create 0640 appuser appuser
    }
    
    copytruncate 会先将当前日志复制为备份,随后把原文件截断为 0 字节,从而保证原进程的文件句柄不失效,避免产生 (deleted) 孤儿文件。

5.2 建立自动化巡检与监控机制

在服务器的日常监控或 Node Exporter 巡检脚本中,可增加未释放大文件句柄的采集指标,提前预防磁盘隐性耗尽:

# 监控脚本示例:统计全机 deleted 文件总占用(单位 MB)
DELETED_SIZE_MB=$(lsof | grep '(deleted)' | awk '{sum += $7} END {print int(sum/1024/1024)}')
if [ "$DELETED_SIZE_MB" -gt 5120 ]; then
    echo "WARNING: Deleted files holding over 5GB disk space: ${DELETED_SIZE_MB}MB"
fi

6. 总结

df 与 du 统计不一致是 Linux 存储设计中 inode 与引用计数的必然体现。遇到磁盘虚假打满时,牢记三步定位排查法:

  1. 查:lsof +L1 或 lsof | grep '(deleted)' 定位持有被删文件的 PID 与 FD;
  2. 消:优先使用 : > /proc/<PID>/fd/<FD> 在线清空,避免直接重启业务;
  3. 防:统一引入 logrotate copytruncate 机制,杜绝手动 rm 活跃日志。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。