Linux磁盘空间满了但du统计对不上的解决方法(lsof找回已删除未释放文件)

Linux磁盘空间满了但du统计对不上的解决方法


df -h报根分区已用95%,可du -sh /*把所有目录加起来才30多G,这种对不上的情况十有八九是文件被删了但还被进程握着句柄。之前在线上一台CentOS 7.9的机器上碰到过,df显示90%满,du却只有30G,折腾了快一小时才定位到元凶。

先确认是不是这个原因

用lsof把「已删除但仍被进程打开」的文件捞出来。lsof 4.87(CentOS 7自带那个版本)支持+L参数,数值是链接数,+L1表示只列出链接数少于1、也就是已经被删但还被占着的文件:

lsof +L1 | grep deleted

grep deleted会列出COMMAND、PID、USER、SIZE/OFF那几列。重点看SIZE/OFF列,被删的大文件往往躺在这儿。当初有一回就是某个Java应用的日志被rm了,句柄没释放,硬占着20多G,df一直红。

两种处理方式

能重启进程就重启,systemd的单元用systemctl restart把句柄放掉,df自然就降了。生产上不想动服务,就直接把那个文件描述符清空:

truncate -s 0 /proc/1234/fd/5

这里走/proc文件系统。/proc/<pid>/fd/<fd>指的就是被删文件还活着的那个句柄,用truncate方式把内容抹成0,磁盘占用立刻释放,进程也不会崩。注意这只是应急,文件大小变0后进程继续往句柄里写会重新撑大。

这个坑踩过不止一次。

为什么du和df对不上

du统计的是能顺着目录项走到的文件占用的块,文件没了目录项它就数不到。df看的是文件系统超级块里记录的已用块数,只要inode还被进程引用着,这块空间就还算「已用」。kernel 3.10.0(CentOS 7默认那个)这套逻辑没变过,从ext4(Linux 2.6.28,2008年合入主线)时代起,删了文件df不降就是预期行为,不是bug。

验证与预防

处理完再跑df -h,看Used列有没有掉下来;同时lsof +L1 | grep deleted应该清空。要长期避免,日志轮转用logrotate配copytruncate,它先复制再清空原文件,不会让正在写的进程丢失句柄。util-linux 2.23、logrotate 4.80(CentOS 7自带)都够用,nginx、tomcat的日志都这么处理。

日常排障可以落一条命令,筛出大于100M的已删除文件,省得肉眼翻:

lsof +L1 | awk '$5>104857600 {print $1,$2,$5}'