探底df和du命令统计磁盘使用结果不一致

现象:

      最近有一台主机,磁盘不断告警,提示磁盘空间使用大于95%,告警邮件如下图 [attach]131[/attach]       然后我就登陆服务器查看,然后df -h 和du -csh统计结果如下所示:
[root@JAVA_HOST1 /]# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        20G   18G  873M  96% /
tmpfs           1.9G     0  1.9G   0% /dev/shm
/dev/vdb        197G  105G   83G  57% /data
[root@JAVA_HOST1 /]# du -csh ./*
6.6M	./bin
41M	    ./boot
4.0K	./boot_ucloud
105G	./data
156K	./dev
26M	    ./etc
4.0K	./home
130M	./lib
21M	    ./lib64
16K	    ./lost+found
4.0K	./media
4.0K	./mnt
24K	    ./opt
du: 无法访问"./proc/3741/task/3741/fd/4": 没有那个文件或目录
du: 无法访问"./proc/3741/task/3741/fdinfo/4": 没有那个文件或目录
du: 无法访问"./proc/3741/fd/4": 没有那个文件或目录
du: 无法访问"./proc/3741/fdinfo/4": 没有那个文件或目录
0	    ./proc
168K	./root
16M	    ./sbin
4.0K	./selinux
4.0K	./srv
513M	./swapfile
0	    ./sys
13M	    ./tmp
982M	./usr
378M	./var
107G	总用量
      从du命令统计的结果可以看出,除掉/data分区的105G使用量,/分区的使用大小应该为2G,但是df查看却使用了18G,这明显不正常,那下面让我们来看看到底是为什么?

浅谈原理

du的工作原理       du命令会对待统计文件逐个调用fstat这个系统调用,获取文件大小。它的数据是基于文件获取的,所以有很大的灵活性,不一定非要针对一个分区,可以跨越多个分区操作。如果针对的目录中文件很多,du速度就会很慢了。   df的工作原理       df命令使用的事statfs这个系统调用,直接读取分区的超级块信息获取分区使用情况。它的数据是基于分区元数据的,所以只能针对整个分区。由于df直接读取超级块,所以运行速度不受文件多少影响,所以比较快速。

实验模拟说明

创建并删除文件       创建文件前的磁盘容量情况:      
# df -h
文件系统                容量  已用   可用  已用%  挂载点
/dev/sda1              12G  5.7G  5.5G  51%   /
tmpfs                  506M     0  506M   0% /dev/shm
      创建文件:
# dd if=/dev/zero of=test.iso bs=1024k count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 14.3055 seconds, 73.3 MB/s
      现在的磁盘情况:
文件系统                容量  已用   可用   已用%  挂载点
/dev/sda1              12G  6.7G   4.6G  60%   /
tmpfs                  506M     0  506M   0%  /dev/shm
      模拟某个进程正在使用该文件:
# tail -f /tmp/test.iso
      删除该文件       打开另一个终端,登陆到系统中。查看是否有进程正在使用上面创建的文件:
# lsof |grep test.iso
tail      2175      root    3r      REG        8,1 1048576000     752972 /tmp/test.iso
      把该文件删掉,并确认:
# rm /tmp/test.iso
rm:是否删除 一般文件 “/tmp/test.iso”? y
# ls /tmp/test.iso
ls: /tmp/test.iso: 没有那个文件或目录
      查看是否还有进程在使用(注意结尾的标记):
# lsof |grep test.iso
tail      2175      root    3r      REG        8,1 1048576000     752972 /tmp/test.iso (deleted)
      查看磁盘使用情况:
# df -h
文件系统                容量  已用  可用   已用%  挂载点
/dev/sda1              12G  6.7G  4.6G  60%   /
tmpfs                 506M     0  506M   0%   /dev/shm
# cat /proc/diskstats |grep sda1
   8    1 sda1 54385 5184 1626626 130090 20434 635997 5251448 5345733 0 111685 5475829
      可见,虽然从ls 已经无法找到该文件,但因为tail 进程仍在使用该文件,故实际上内核并没有把这文件所占用的空间释放出来(df 结果和删除文件之前是一样的)。   停止tail进程:       回到第一终端,用Ctrl+C 终止tail 进程,查看结果:
# df -h
文件系统              容量  已用 可用 已用% 挂载点
/dev/sda1              12G  5.7G  5.5G  51% /
tmpfs                 506M     0  506M   0% /dev/shm
# cat /proc/diskstats |grep sda1
   8    1 sda1 54473 5184 1627402 130617 20453 636042 5251960 5345756 0 112226 5476379
      至此,文件所占用的空间已完全释放。 实验结果:
1、若有进程在占用某个文件,而其他进程把这文件删掉,只会删除其在磁盘中的标记,而不会释放其占用的磁盘空间;直到所有访问该文件的进程退出为止;
2、df 是从内核中获取磁盘占用情况数据的,而du是统计当前磁盘文件大小的结果,由于磁盘标记已被删掉,因此du 不会计算上述被删除文件的空间,导致df 与 du的结果不一致。

解决问题

1、把占用文件的相关进程关闭       通过下面的命令得到这些已被删除,但未释放空间的文件和进程信息,然后删除  
# lsof |grep deleted 
找到这些进程后,在安全的情况下把其kill,空间自会马上释放。
# lsof |grep deleted |awk '{print $2}'|xargs -n1 -t kill -9
2、以清空的方式替代删除       归根到底,产生问题的原因是,访问该文件的文件指针(句柄),在rm 动作后,因为进程仍在访问,因此,仍处在文件里面(中间或结尾处)。所以,如果用清空的方式,把文件指针重置,该文件所占用的空间也会马上释放出来。
# echo > /tmp/test.iso
# df -h
文件系统              容量     已用 可用  已用% 挂载点
/dev/sda1              12G  5.7G  5.5G  51% /
tmpfs                 506M     0  506M   0% /dev/shm
# tail -f /tmp/test.iso
tail: /tmp/test.iso: file truncated
      所以,对于常发生类似问题的文件,如:日志记录文件等。以改名、清空、删除的顺序操作可避免该问题发生。

教育意义

当出现du和df差距很大的情况时,考虑是否是有删除文件未完成造成的,方法是lsof命令,然后停止相关进程即可。 (2)可以使用清空文件的方式来代替删除文件,方式是:echo > test.iso。 (3)对于经常发生删除问题的日志文件,以改名、清空、删除的顺序操作。 (4)除了rm外,有些命令会间接的删除文件,如gzip命令完成后会删除原来的文件,为了避免删除问题,压缩前先确认没有进程打开该文件。[/code]

文件空洞

  文件读写时,如果先文件指针偏移很大一段,然后写入1byte;这样这个文件实际占用1byte空间,但是stat查看文件大小,或者读写时,都会发现文件很大;所有没有写内容的都返回0,且不占用空间,这样的文件叫 'sparse file',即文件空洞
  容易发生在一个进程在写一个文件,这是人工进行清空文件操作,就会产生。


2 个评论

过程详细。生产案例比较实用
你也可以分享! 一起学习。

要回复文章请先登录注册