Linux服务器磁盘中存在大量小文件,需要进行删除(文件小于1K,数量50w~100w),发现rm删除速度奇慢无比,甚至出现“argument list too long”的错误。网上资料一搜索基本都是建议rsync方法,所以本文对几种常见的方法进行试验对比。
1)superblock:记录文件系统的整体信息,包含inode/block的大小、总量、使用量、剩余量,以及文件系统的格式,文件系统挂载时间,最近一次数据写入时间,最近一次校验磁盘的时间等。
2)inode:表示文件系统的对象,具备唯一标识符(一个文件占用一个inode),inode记录了文件的元信息,具体来说包含的信息为:文件所有者、访问权限(读、写、执行)、类型(是文件还是目录)、内容修改时间、inode修改时间、上次访问时间、对应的文件系统存储块(Block)的地址;
3)block:实际记录文件的内容,若文件太大,则会占用多个block,通常的block大小有1K,2K,4K三种,这里内核记录block信息的数据结构是Bitmap。
ext2 为索引式文件系统,新增一个文件的流程如下:
1)确定目录是否有写权限/打开权限(w/x),没有则返回失败;
2)根据inode-bitmap分配新的inode,将新文件权限/属性记入;
3)根据block-bitmap分配新的block,将文件数据写入block中,更新inode的block指向数据;
4)将新的inode、block数据同步到inode-bitmap、block-bitmap中,并更新superblock内容;
ext3 为日志式文件系统,对ext2的缺点进行改进(系统故障重启,修复元数据信息耗时长),多出一块日志记录区块来保证可靠性:
1)当系统准备写入一个文件时,先在日志记录区块记录文件要写入的信息;
2)写入文件权限/属性,写入文件数据,更新元数据内容(同ext2);
3)完成数据与元数据更新后,在日志记录区块完成文件的记录;
在出现故障需要恢复时,可根据日志追踪之前提交到主文件系统的更改,大大减少了磁盘的扫描时间,实现丢失数据的快速重建,比传统的索引式文件系统更安全[3]。Linux下的集中日志式文件系统有XFS(目前是CentOS7的默认文件系统),ReiserFS,Ext3,Ext4。
CPU:4核,Intel(R) Core(TM) i3-3120ME CPU @ 2.40GHz
内存:8G
硬盘:1T(ST1000NM0055)
操作系统:Linux-3.10.25
文件系统:ext3
软件版本:find、rm(busybox1.24)、rsync3.1、bash3.2、perl5.8
1)创建文件:创建文件夹test,生成$num个8字节的文件,为防止文件名有规律,文件名给定随机的前缀,并且每次生成完成后都清空内存cache(echo 3 >/proc/sys/vm/drop_caches)
function genfiles()
{
mkdir test
for ((ix=0; ix<$num; ix++)) do
local filename=$RANDOM
echo -n "01234567" >test/$filename-$ix
echo "Genfile($ix/$num): $filename"
done
}
2)删除文件:根据文章[1]给出的方法,实验各种删除方法,并使用time给出统计时间
time rm -rf test
time find test -type f -delete;
time perl -e 'for(<*>){((stat)[9]<(unlink))}'
time ./rsync -a --delete $PWD/tmp/ $PWD/test/
3)生成100w个文件,测试结果如下:
测试方法 | 耗时 |
---|---|
rm -rf test | 11m12.703s |
find test -type f -delete; | 13m18.632s |
cd test && perl -e 'for(<*>){((stat)[9]<(unlink))}' | 19m17.510s |
./rsync -a --delete $PWD/tmp/ $PWD/test/ | 17m54.199s |
结果并没有跟文章[1]描述的情况一致,rsync快速提升?!
根据文件系统元数据的思路,考虑我生成100w小文件的时候,名字为随机命名的,但是inode号为正向顺序增长的(猜测blocks块按照一定规律写入,机械硬盘的磁头使用自身高效的方式进行写入),如果删除文件时按照inode号顺序进行排序效率会怎么样?思路如下:
1)遍历目录,获取文件列表,按照inode号大小排序;
2)按照文件列表删除文件;
3)两种方案进行速度比较:inode排序(-DORDER_BY_INODE)、文件名排序;
遍历目录,将文件插入到红黑树regtree中,目录插到vector中:
static int __dir_scan(const char *pname, REGTREE ®tree, vector &dirlist)
{
int cnt = 0, ret = 0;
DIR *dir = NULL;
struct dirent *entry = NULL;
dir = opendir(pname);
assert(dir);
printf("dir: %s\n", pname);
while ((entry = readdir(dir)) != NULL) {
if (0 == strcmp(entry->d_name, ".") ||
0 == strcmp(entry->d_name, "..")) {
continue;
}
string fname = pname;
fname.append("/");
fname.append(entry->d_name);
if (entry->d_type == DT_DIR) {
ret = __dir_scan(fname.c_str(), regtree, dirlist);
if (ret <= 0) {
cnt = 0;
}
else {
cnt += ret + 1;
}
dirlist.push_back(fname);
}
else {
#ifdef ORDER_BY_INODE
regtree.insert(pair(entry->d_ino, fname));
#else
regtree.insert(pair(fname, fname));
#endif
cnt++;
}
if (regtree.size() >= BATCH_SIZE) {
break;
}
}
closedir(dir);
return cnt;
}
根据dirlist删除目录、regtree删除文件:
static int __dir_remove(vector &dirlist)
{
int cnt = dirlist.size();
for (size_t ix = 0; ix < dirlist.size(); ix++) {
LOGN("rmdir: %s\n", dirlist[ix].c_str());
rmdir(dirlist[ix].c_str());
}
// dirlist.clear();
return cnt;
}
static int __reg_remove(REGTREE ®tree)
{
int cnt = regtree.size();
for (REGTREE::iterator iter = regtree.begin();
iter != regtree.end(); iter++) {
LOGD("unlink: %llu %s\n", iter->first, iter->second.c_str());
unlink(iter->second.c_str());
}
regtree.clear();
LOGN("Files: %d\n", cnt);
return cnt;
}
为了防止内存占用过大,100w文件列表的内存使用超过1Gb,期间对regtree.size()进行限制,当扫描文件列表达到10w后,先退出扫描,先把文件删除一部分后再继续
#include
#include
#include
#include
#include
#include
上面测试程序写的比较糙,vector没有对dirlist进行去重,多次扫描还会多次rmdir同一个目录;中间使用了assert,实际过程可能还会出现文件删除不掉,文件夹陆续还有新文件出现的情况;
测试数据一看还是蛮不错的,相比rsync、rm、find方法快了许多:
测试方法 | 耗时 |
---|---|
./rm_by_inode test | 0m53.135s |
./rm_by_name test | 9m55.988s |
这本先是根据文章[1]进行了实验,后来发现实验结果完全与文章不一致,又查看了一些文件系统的资料,后来自己写了代码实验了一下,最终发现ext3文件系统下,inode号与磁头处理效率是挂钩的,特此将步骤写下,后续工程中再继续实践~
参考文章:
[1] https://www.slashroot.in/which-is-the-fastest-method-to-delete-files-in-linux
[2] https://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/
[3] https://zhuanlan.zhihu.com/p/22976640