📖 先唠两句
大家好啊,我是阿垚。
欢迎来到《100个“反常识”经验》第02期。
上期聊了SSH连不上,防火墙背了90%的锅。今天聊一个更诡异的问题:
磁盘满了,但du找不到大文件。
你是不是也遇到过——df -h显示使用率95%,但du -sh *加起来连一半都不到?
更离谱的是,明明把文件删了,磁盘空间却一点没变。
别急,今天带你找到真凶。
🤔 一个让我怀疑人生的下午
那天监控报警:某台生产服务器磁盘使用率飙到96%。
我登录上去,先执行了常规操作:
bash
df -h du -sh / 2>/dev/null | sort -hr | head -10
结果傻眼了:df显示用了180G,du加起来只有90G。
那90G去哪了?
我又去翻了/tmp、/var/log、~/.cache……都没有。
折腾了一个多小时,最后发现是什么?
有一个大文件被删了,但被某个进程还攥在手里,空间根本没释放。
这就是Linux里经典的“文件已删除,句柄未释放”问题。
🔧 为什么会出现这种情况?
简单解释一下原理:
当一个进程打开一个文件后,即使你在磁盘上把它删了(rm),只要进程还在运行,文件占用的空间就不会释放。
因为Linux的文件系统是通过inode引用计数来管理的。rm只是删掉了目录项,引用计数减1;但如果进程还持有这个文件,引用计数不会归零,空间就不会回收。
所以你会看到:文件没了,空间也没了。
📝 排错三步法(建议收藏)
第一步:确认是不是“幽灵文件”
执行以下命令,查看被删除但未释放的文件:
bash
lsof | grep deleted
或者更精确地只看文件大小:
bash
lsof | grep deleted | awk '{print $7, $9}' | sort -hr如果看到一大堆(deleted)标记的文件,恭喜你,找到真凶了。
第二步:找到罪魁祸首进程
从上面输出里找到进程ID(PID),比如:
bash
java 12345 root 67r REG 8,1 10737418240 123456 /var/log/app.log (deleted)
这里PID是12345,是一个Java进程,删了一个10GB的日志文件但没释放。
确认一下进程在干什么:
bash
ls -l /proc/12345/fd/ | grep deleted
这会列出该进程所有还攥着的已删除文件。
第三步:释放空间(两种方法)
方法一:重启进程(推荐在业务低峰期)
bash
systemctl restart 服务名 # 或者直接 kill -9 12345 再重新启动
重启后,进程释放所有文件句柄,空间就回来了。
方法二:清空文件而不是删除(无需重启)
如果进程不能重启,可以这样操作:
bash
cat /dev/null > /proc/12345/fd/67
其中67是上面输出中的文件描述符编号。这条命令会清空那个文件,空间立即释放,进程继续运行。
⚠️ 注意:这个方法需要确认清空的文件确实是不需要的,别把正在写的重要数据清了。
💡 永久防范方案(别等磁盘满了再慌)
做完以上排查,我建议你再做四件事:
✅日志轮转配置好:使用logrotate,设置copytruncate选项,避免进程锁文件
✅监控文件句柄:添加告警,当lsof | grep deleted累计超过一定阈值时通知
✅应用日志规范:要求开发使用日志滚动框架(如Log4j的RollingFileAppender),别自己手写日志逻辑
✅定期巡检脚本:每周执行一次,检查是否有“幽灵文件”
bash
#!/bin/bash count=$(lsof 2>/dev/null | grep -c deleted) if [ $count -gt 0 ]; then echo "发现 $count 个未释放的文件句柄" lsof 2>/dev/null | grep deleted | head -10 fi
🔜 下期预告
《反常识03:服务器时间跳变导致K8s证书失效——凌晨3点的惊魂30分钟》
评论区聊聊你遇到过最诡异的磁盘问题。
——阿垚,一个踩过2万次坑的“老”IT