1、问题
使用du 和df 分别对/etc 统计其大小,发现统计结果差距很大?何因?
[root@P1QW01 ~]# df -h /etc/ Filesystem Size Used Avail Use% Mounted on /dev/mapper/rootvg-rootlv 4.8G 249M 4.4G 6% / [root@P1QW01 ~]# [root@P1QW01 ~]# du -sh /etc/ 31M /etc/ [root@P1QW01 ~]#
--------------------update 2020年7月30日15:10:13------------------------------------------
最近发现目录空间不足的报警,查看一番发现df 统计的结果和du差距很大。
home]# du -sh * 220K ansibleDemo 28K cp.log 172M gpadmin 4.0K host.sh 36K ktabrm 36K ktahis 40K ktappt 36K ktauas 16K lost+found 1.4M oracle 637M scripts home]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/root_vg-rootlv 9.8G 2.9G 6.4G 31% / tmpfs 127G 216K 127G 1% /dev/shm /dev/sda2 477M 36M 416M 8% /boot /dev/sda1 500M 272K 500M 1% /boot/efi /dev/mapper/root_vg-homelv 20G 16G 2.8G 86% /home
2、结论
原因是:
df 统计的时候是从文件系统考虑的,不仅包含文件系统大小,还要统计被命令或者程序占用的空间,
例如 文件已经被删除,但是被某个进程持有。其原理是读取每个分区的superblock来获取空闲数据块、已使用数据块,从而计算出空闲空间和已使用空间,因此df统计的速度极快(superblock才占用1024字节)。所以非常快。
q1:为什么文件被删除了df -h 还可以统计的到?
要回答这个问题需要很多基础知识,以后有空再写两篇,这里简单说一下,只要bmap中不将这个文件的data block标记为未使用,就会算到实际使用的空间中。bmap是元数据区的一个位标记,其中记录的是数据区的block是否被使用。
du 是面向文件的,只计算文件或者目录占用空间,其具体做法就是stat 文件,然后统计其所占的实际物理空间,因此可以跨分区统计某些你想统计的文件大小总和。因为它们都能被stat找到并统计。
codis]# find / -type f -name "*.conf" -print0 | xargs -0 du -csh 8.0K /datap11/gpseg0/pg_hba.conf 4.0K /datap12/gpseg2/pg_hba.conf 12K total
如果文件被删除了,这种肯定是统计不到的。因此看到的du 统计的使用更少,而df 统计的结果更多,可能是因为文件被程序持有。
3、如何找出“丢失的空间”?
既然文件已经被删除了为何还不释放呢? 文件已经被删除,但是被某个进程持有。这种情况就不会释放。
如何查找出这些僵尸文件呢?
regExp]# lsof |grep deleted
找到的结果就是一些文件已经被删除,但是空间还没有释放的。可以很久实际情况将持有的改文件的PID kill掉来释放空间。
可以使用如下命令批量删除。 生产环境谨慎操作。
for i in `lsof |grep deleted |awk '{ print $2 }'` ; do kill -15 $i ;done
至此,可以解决大多数问题,在网上找了很多都没有提到如果还是解决不了应该怎么办?
有的说是要重启服务器,这就.............
经过一番查询。发现 du -sh* 并不包括隐藏文件和目录。
果然 .ansible占了16GB,至此凶手找到了,ansible 的tmp文件删除就可以了。
.ansible]# du -sh * 16G tmp
再仔细看一下gp一个segment的录下的文件数量我惊呆了。40W+ 的文件目录数量,总共30GB,直接报错了。
ansible]# ls tmp/|wc -l 406278 .ansible]# time rm -rf ./tmp/* -bash: /bin/rm: Argument list too long real 0m7.728s user 0m4.686s sys 0m0.408s
如何解决呢?通过传参的方式解决
.ansible]# time find /home/.ansible/ -name "a*" | xargs rm -rf "a*" real 33m10.841s user 0m7.251s sys 0m53.530s
有没有更快的办法呢?
经过查询可以使用rsync ,使用--delete-before, -d 后跟两个目录,前一个目录为一个空目录,后一个目录为需要删除的目录。
经过测试,时间大约节省了一半的时间。
rsync 的原理大致是: 接收端的rsync会先删除目标目录下已经存在,但源端目录不存在的文件。也就是"多则删之,少则补之"。
如果是"--delete-before",则在目标端rsync刚启动时,会比较源端和目标端,对本利来说,经过比较发现目标端的文件夹是空的,那就是说源端的那个目录/home/.ansible/tmp/需要全部删除,于是就开始删除了。
30G /home/.ansible/tmp tmp]# ll |wc -l 410049 tmp]# time rsync --delete-before -d /home/.ansible/tmp_1/ /home/.ansible/tmp/ real 14m7.573s user 0m6.763s sys 1m6.309s 也可以使用另外一个参数差别不大。 ll |wc -l 410515 tmp]# df -h /home/.ansible/;echo;du -sh /home/.ansible/ Filesystem Size Used Avail Use% Mounted on /dev/mapper/rootvg-homelv 201G 42G 149G 22% /home 30G /home/.ansible/ You have new mail in /var/spool/mail/root tmp]# ll |wc -l 410597 tmp]# time rsync -a --delete /home/.ansible/tmp_1/ /home/.ansible/tmp/ real 13m42.883s user 0m6.075s sys 1m4.439s
在这里想说的是,如果都是文件的话,删除速度更快一些。看如下几个例子。
redis]# for i in $(seq 1 410000);do echo text >>$i.txt;done redis]# time rsync -a --delete /redis/tmp/ /redis/ real 0m46.074s user 0m0.336s sys 0m44.341s 使用python删除 import os import timeit def main(): for pathname,dirnames,filenames in os.walk('/redis/tmp/'): for filename in filenames: file=os.path.join(pathname,filename) os.remove(file) if __name__=='__main__': t=timeit.Timer('main()','from __main__ import main') print t.timeit(1) tmp]# time python /home/scripts/delansbieltmp.py 13.6427898407 real 0m13.663s user 0m1.733s sys 0m10.006s
结论: 如果删除大量小文件,速度还是很快,使用rsync 就可以解决,如果有多级目录,删除起来会比较慢。
4、du和df的原理
du -s命令通过将指定文件系统中所有的目录、符号链接和文件使用的块数累加得到该文件系统使用的总块数,因为是stat所以所有类型的 ”文件” 都可以统计的到;而df命令通过查看文件系统磁盘块分配图得出总块数与剩余块数。文件系统分配其中的一些磁盘块用来记录它自身的一些数据,如i节点,磁盘分布图,间接块,超级块等。这些数据对大多数用户级的程序来说是不可见的,通常称为Meta Data。
du命令是用户级的程序,它不考虑Meta Data,而df命令则查看文件系统的磁盘分配图并考虑Meta Data。df命令获得真正的文件系统数据,而du命令只查看文件系统的部分情况。