高效清理文件系统大量文件的方法

线上环境找出大量消耗inode的文件目录,我们需要有一个快速清理文件的方法。

参考 Delete only files older than 7 days: -mtime and find

find /tmp/exec -maxdepth 1 -mtime +1 -type f -delete

如果要检查文件而不是删除,可以将 -delete 改成 -print

find /tmp/exec -maxdepth 1 -mtime +1 -type f -print

但是发现不行

#df -i | grep vda
/dev/vda1        2621440 2618372      3068  100% /

[root@server-1.example.com /root]
#find /tmp/exec -maxdepth 1 -mtime +1 -type f -delete

[root@server-1.example.com /root]
#df -i | grep vda
/dev/vda1        2621440 2564624     56816   98% /

也就是说都是一天内生成的文件

参考 find -mtime files older than 1 hour [duplicate] 改成一小时

原先案例:

find /var/www/html/audio -daystart -maxdepth 1 -mmin +59 -type f -name "*.mp3" \
    -exec rm -f {} \;

如果要在删除前先验证可以使用echo命令加入

... -exec echo rm -f '{}' \;
          ^^^^ Add the 'echo' so you just see the commands that are going to get
               run instead of actual trying them first.

我执行:

find /tmp/exec -maxdepth 1 -mmin +59 -type f -exec rm -f {} \;

不过,这个命令执行起来超级慢。我测试了一下,如果要删除200w+文件,需要大约1.5小时。用watch每2秒检查一下文件清理速度,似乎每秒钟只清理350~400个文件。(计算了一下,每秒400个文件的话,1.5小时确实只能删除 216万 个文件)

为了加快清理速度,我找到 Efficiently delete large directory containing thousands of files

有以下一些推荐方法:

  • 较快的删除目录文件方法是使用 rsync 对一个空目录和需要删除文件目录进行同步,可以快速清理掉目录下的文件

mkdir empty_dir
rsync -a --delete empty_dir/ yourdirectory/

但是,这个方法对于我的案例不适用

此外,更快的方法是使用perl,但是我不熟悉perl,没有把握使用这个命令

cd yourdirectory
perl -e 'for(<*>){((stat)[9]<(unlink))}'
  • 有推荐使用 find 命令的 -delete 参数,据说比标准的 -exec rm -f {} \; 要快。但是这个命令不是所有平台通用(GNU find才支持 -delete),并且也是递归方式删除

  • 可以通过 xargs 来加速删除,因为 xargs 可以并发删除文件,也就是你可以一次传递多个文件给xargs,执行一条删除命令:

find /path/to/folder -name "filenamestart*" -type f -print0 | xargs -0rn 20 rm -f
  • 这里 xargs 的参数 -0 表示输入项是以null字符结束而不是空白,并且引号和反斜杠不指定。这个参数适合输入项可能包含空格,引号和反斜杠情况。

  • -r, --no-run-if-empty 通常即使没有输入也会运行一次,GNU扩展的这个参数在没有包含任何非空白,就不运行

  • -n 20 表示最大参数20个

上述参数结合起来就是表示输入给xargs的一次处理参数是20个,也就是20个并发删除。你可以调整大这个参数以便更大并发。

不过,根据 man xargs 手册提示,xargs能够自动根据平台计算出缓存大小,来决定尽可能大的参数,所以可以不用加 -n 20 而是让 xargs 自动判断取最大并发。所以可以改为 | xargs -0r rm -f

对于我这个案例可以修订脚本命令

find /tmp/exec -maxdepth 1 -mmin +59 -type f | xargs -0r rm -f

测试对比

  • 先创建一个目录下50w文件

mkdir test1
cd test1
for i in $(seq 1 500000); do echo testing >> $i.txt; done

大约15秒就可以创建好500w文件

  • 删除测试常规rm

time find . -type f -exec rm {} \;

完成时间

real    10m51.132s
user    1m56.469s
sys    8m49.892s

再次测试

real    11m36.329s
user    1m37.385s
sys    9m53.831s
  • 测试find的-delete

time find . -type f -delete

神奇,达到了xargs的效率

real    0m10.493s
user    0m0.692s
sys    0m5.105s
  • 测试删除: xargs并发20个文件

time find . -type f | xargs -0rn 20 rm -f

实测不行,传递参数过长,导致报错 xargs: argument line too long

但是可以改成 find 加上 -print0 参数

time find . -type f -print0 | xargs -0rn 20 rm -f

删除效率也有很大提高,仅26秒钟,虽然比不上让xargs自动选择buffer的方式

real    0m26.355s
user    0m8.334s
sys    0m15.133s
  • 测试删除: xargs自动使用buffer并发删除文件

time find . -type f | xargs -0r rm -f

实测不行,传递参数过长,导致报错 xargs: argument line too long

time find . -type f -print0 | xargs -0r rm -f

神奇的效率,仅仅12秒钟就可以完成删除

real    0m11.995s
user    0m0.984s
sys    0m5.603s

批量处理

如果有大量的服务器需要检查inode消耗,我们可以使用 pssh 工具:

  • 例如对于案例,执行 pssh -ih hosts 'df -i | grep vda1' 输出到日志文件 check_disk.log 如下:

192.168.6.172:/dev/vda1        2621440 2613825      7615  100% /
192.168.6.239:/dev/vda1        2621440 2614148      7292  100% /
  • 过滤日志进行处理

cat check_disk.log | awk '{print $1","$5}'

获得类似输出

192.168.6.192:/dev/vda1,96%
192.168.6.200:/dev/vda1,5%
192.168.6.202:/dev/vda1,84%
  • 再进行数据整理

cat check_disk.log | awk '{print $1","$5}' | awk -F"[:,%]" '{print $1","$3}'

就获得了如下格式

192.168.6.183,5
192.168.6.165,82
192.168.6.168,6
...
192.168.6.192,96
192.168.6.200,5
192.168.6.202,84

现在进行判断参考 print line only if number in third field is greater than X [duplicate] 或者参考我之前的实践记录 awk搜索字符串

cat check_disk.log | awk '{print $1","$5}' | awk -F"[:,%]" '{print $1","$3}' | awk -F, '$2>10 {print $1}'

awk 支持先判断,然后执行打印 参考 get all rows having a column value greater than a threshold

所以执行

cat check_disk.log | awk '{print $1","$5}' | awk -F"[:,%]" '{print $1","$3}' | awk -F, '$2>10 {print $1}' | tee ip_clean
  • 现在我们可以开始批量清理了

pssh -t 0 -ih ip_clean 'sudo find /tmp/exec -maxdepth 1 -mmin +10 -type f -delete'

参考

Last updated