一句话解释是因为删除的文件正在使用
关键词
- 文件已删除,但是df没有正确显示磁盘剩余容量
- lsof | grep deleted
- 磁盘剩余空间明显不符合预期
- 删除一个大文件,但还是提示磁盘空间已满(No space left on device)
问题复现
在正常情况下,我们使用df -i
和df -h
,查看磁盘inode和block,如下图所示,一切正常
现在我们考虑一个特殊情况,我们往系统日志里写入约5个G的测试文件,比如使用下面这条命令,测试需谨慎,我这是5个亿,机器CPU负载会飙升
seq 500000000 >> /var/log/messages
执行成功后使用df查看系统磁盘信息,如下图所示
现在我们使用rm /var/log/messages
删除这个正在使用的系统日志文件,然后使用df对比磁盘信息。
可以看到,我们虽然删除一个接近5个G的系统日志文件,而且通过文件名也确实找不到这个文件,但是我们的inode和block,无任何变化,咋一看好像我们的df似乎"失效了",其实不是。我们知道要彻底删除一个文件,需要将其所有的硬连接(包含自身),全部移除。这里我们就可以这样理解,/var/log/messages是系统日志文件,系统的相关服务正在使用,而系统使用这个文件是通过inode来访问这个文件的,在我们删除这个文件之前系统已经知道了该文件的inode,我们现在删除/var/log/messages,只相当于移出了一个文件名,该文件的inode因为系统正在使用而并没有消失,同理我们的df也是通过inode统计磁盘信息的,因此无变化。
神坑就此而诞生。我正在 windows下使用word书写这篇文章,如果我现在删除这篇文章,windows将会提示
可以看到windows下,删除一个正在使用的文件,将执行失败,并给出如上图所示的提示,看起来windows下似乎没有此坑。至于Linux开发者为什么设计了这种特性,不得而知,我们目前也不关心。下面谈谈,怎么知道自己掉坑里了。
如何知道df -h显示的磁盘剩余容量并不是真正的磁盘剩余容量 ?
上面那个坑是我们人为复现的,假如我们事先并不知道有人误删除一个正在被使用的超大文件,该如何确定df -h
显示的磁盘容量,是否就是真实的磁盘容量呢?
方法1 利用du命令
假设我们现在已经掉坑里了,也就是续上我们上面所模拟的实验,已经删除了一个系统正在使用大日志文件/var/log/messages,由于df命令是根据inode是否存在作为实现基础,因此这里我们不用它了,而用du,我们知道du -sh dir
,可以统计dir目录及其子目录所有文件的大小,相当于在windows下对一个文件夹执行右键>>属性,查看其大小。我们就利用du来查看一下整个系统所有文件到底有多大。
“右键一下根” du -sh /
“右键一下根下所有目录” du -sh /* | sort -hk1 | tail
上面两张图中关于虚拟目录一些文件找不到的提示,直接忽略。对比上面两张图,以及参考上文我们模拟的数据,可以看到,df告诉我们用了7.0G空间,而du不管是整体根目录的大小,还是我们一个个累加根下所有目录的大小,都可以明确看出,我们只用了2.4G空间,而且通过对比我们还可以发现,缺少的4.6G空间,正是我们删除的系统日志的大小。
小结:df是根据inode来统计磁盘使用情况,当误删了正在使用的文件(比如我们模拟的系统日志),会导致df命令显示异常。而du命令是根据文件和目录名来统计磁盘大小的,不会有此异常。
du命令可以推测系统中有被误删的正在使用的文件,但是还不够直观。无法准确告诉我们到底是哪个文件在运行中被删除了,另外如果系统里的文件很多,使用du -sh /
将会非常慢,并且消耗一定的系统资源
方法2 利用lsof命令
lsof命令单独使用,是告诉我们,系统中所有被打开的文件。类似与下面这样
巧了,比如说我们上面模拟的/var/log/messages系统日志文件,没删除前它就是被系统正在打开使用的,可以使用lsof | egrep "COMMAND|messages"
,来查看,如下图所示
删除后,由于系统仍在使用这个文件,因此可以在lsof命令中看到,并且系统还贴心的为其添加上deleted标志,方便我们识别,可以使用lsof | egrep "COMMAND|deleted"
,直接看到,如下所示
上图给出的信息,明确而清晰:有个叫做,messages的超大文件,被我们误删啦。我们不必要知道,被误删的文件名是什么,只需匹配deleted关键词即可
问题解决
明白了原因之后,对症下药,问题将迎刃而解。首先,简单粗暴老套路:重启服务器。在重启系统的瞬间,系统不在占用/var/log/messages,当然该文件也就被彻底删除了,重启后再使用du,df,lsof,这些命令查看,一切都正常了,当然/var/log/messages,会被系统自动新建。
比重启服务更理想的方法,也是伤害更小的方法是:重启对应的服务。事实上重启服务器,也就相当于重启对应的服务,使用/etc/init.d/rsyslog restart,即可重启系统日志服务。同样重启服务后,du,df,lsof,都将显示正常。
不过这是一种事后补救的方案,是在我们不了解Linux删除文件原理的情况下,贸然删除一个正在使用的文件,如果在删除文件前我们通过lsof等确认该文件不再使用,再删除之,则不会遇到上文说的问题,或者我们确实需要删除一个正在被使用的文件(如删除一个200GB的nginx日志文件,该日志正在被写入),我们可以使用如下重定向的方法
echo "" > file.log
这种重定向的方法,不会有删除文件,仍占用磁盘空间的问题