海量文件的rsync同步方案

背景

如果一个环境需要对文件系统进行系统备份,文件系统内的文件数目是10亿级别的,那么直接通过一个rsync进行同步肯定是不太好的,如果出现中断,那么再次启动后的遍历的时间成本太高了

分析

rsync的同步原理是,启动同步的时候,会对源端进行一个全量的扫描,构建一个incremental file list,然后开始同步,如果是系统本地的目录进行同步我们可以看到三个进程
一个是生成器,一个是发送方,一个是接收方,因为是在一台机器上面运行,所以可以看到这三个进程的,这个之前还误以为是并发三,实际还是单进程模式的

我们很多情况下,存储环境都是集群模式的,集群模式就是文件系统有多个入口,可以多个并发同时去操作,比如我们原始集群有2个网关,我们新集群有6个网关,那么我们实际上是可以1个原始网关对3个新网关的方式去处理数据的

由于我们无法去判断原始的目录结构,并且即使能够获取到,也不太好去做均分,比如有6亿文件,根目录下面一个目录5亿,其它5个目录1亿,这种情况就不好去分平分目录了,还有个情况是如果按容量区分,也是不太好取到怎么去区分目录的,所以这个地方想到的一个方案是获取到所有的文件的列表,然后对列表做拆分,然后分配任务的方式

如何实现

如果能够拿到完整的文件列表,然后再对列表拆分就很好拆分了,比如9亿文件,我可以拆分成1000w一个任务,分成90份,然后平分给6个机器上面去,都是很好去做拆分的,拆大拆小都比较自由

获取文件的完整列表

1
time rsync -av --dry-run /source/ /target/  > file_list.txt

通过上面的命令可以完整的拿到文件的列表,/target目录是空目录

拿到的文件的格式如下

1
2
3
4
5
6
7
8
sending incremental file list
./
file1
···
zp/xag

sent 3,169,627 bytes received 600,058 bytes 2,513,123.33 bytes/sec
total size is 819,200,131 speedup is 217.31 (DRY RUN)

也就是这个文件我们需要删掉文件的第一行和文件的倒数两行还有目录
因为这个扫描会把目录扫描出来,我们是要文件同步,如果是包含目录,那么会出现重复的文件,所有的文件同步了,目录自然也是同步的,并且同步的时候目录rsync会自己处理好
如果这个文件是巨大的,那么我们处理文本文件的时候还是要注意下,本篇会考虑这种情况

拆分文件

拆分文件就是根据我们需要的任务数,然后拆分文件,首先需要统计文件的行数

1
2
3
4
5
[root@myserver home]# time wc -l file_list.txt 
918411499 file_list.txt
real 1m31.319s
user 0m7.154s
sys 0m15.842s

统计这个9个亿的文件列表的行数,需要90秒

看下文件的大小

1
2
[root@myserver home]# ll file_list.txt 
-rw-r--r-- 1 root root 49173415850 Aug 23 00:46 file_list.txt

9亿文件列表的大小为45G,这里建议拆分成2G左右一个文件,或者更小,我们拆分2G就是24份,用9亿除以24得到大概得文件数目,然后往高取整得到38267146这个值,也就是单个文件存储大概3800w条数据

我们使用slit命令拆分文件

1
2
3
4
5
[root@myserver zpdisk]# time split -l 38267146 file_list.txt

real 9m18.176s
user 0m6.718s
sys 0m31.634s

拆分的时间大概在10min左右,得到的文件大概2G

我们拆分完文件以后还要处理文件

处理文件列表

拆分出来的文件第一个文件的开头要去掉,最后一个文件的结尾去掉两行,中间每个文件都需要去掉目录

这里我们需要处理三个地方

1
2
3
4
5
[root@myserver zpdisk]# time sed -i '1d' xaa

real 0m51.695s
user 0m10.834s
sys 0m36.708s

处理开头的一行,这个需要大概1min

删除最后一行

1
2
3
4
5
[root@myserver zpdisk]# time sed -i '$d' xax

real 0m50.757s
user 0m11.149s
sys 0m35.621s

删除最后一行需要1min,要操作两次

删除列表里面的目录

1
2
3
4
[root@myserver zpdisk]# time sed -i '/\/$/d' xaa 
real 0m59.833s
user 0m19.544s
sys 0m36.030s

这里一个文件需要处理大概1min,一共24个也就是总共时间24min

处理完成后我们就得到一个完整的文件列表了

使用列表

1
rsync -av /source/ /target/  --files-from=xaa  --log-file=/var/log/xaa.log

一个列表对应一个命令,然后这个具体要并发开几个,这个就是很简单的事情了,一条命令对一个列表即可,剩下的事情就很简单了

补充

上面是同步的所有文件的情况,还有个情况是原始目录里面有可能有很多空目录,这个需要取一下列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@jenkins filelist]# cat checkkong.py
#! /bin/env python
# -*- coding:utf-8 -*-

# 比较方法如下
#判断当前行和上一行
#如果当前行为文件的,那么上一行不管文件还是目录,上一行肯定不为空
#如果当前行为目录的,上一行为目录的
# 如果上一行的目录的字符串包含在当前行里面,那么上一行肯定不为空
# 如果上一行的目录字符串不包含在当前行的,那么上一行就是空目录了

#按这个取空目录的列表


import sys
def compare_lines_in_file(filename):
with open(filename, 'r') as file:
previous_line = None

for current_line in file:
current_line = current_line.strip() # 去除行末尾的换行符或空白符

if previous_line is not None:
if current_line.endswith('/') and previous_line.endswith('/'):
if previous_line in current_line :
pass
else:
print(f"{previous_line}")

# 将当前行设为下一次比较的前一行
previous_line = current_line

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python checkkong.py <filename>")
else:
filename = sys.argv[1] # 从命令行获取文件名参数
compare_lines_in_file(filename)

执行方法

1
time  python3 checkkong.py  filelistfile.txt > kong.txt

删除第一行的./

1
sed -i '1d'  kong.txt

然后同步这个空目录列表即可

1
rsync -av  --files-from=kongdir.list   /zp/source/ /zp/target/

总结

总结下操作流程

  • 拿列表
  • 拆分列表
  • 处理列表
  • 使用列表

整体上的步骤就这四步了

时间和空间数据

上面是基于一个9亿文件的测试环境做的测试,有一些结果可以供参考

测试事项 时间/容量 单项计算时间
9亿文件的获取列表时间 563min 1亿文件需要62min
9亿文件的本地文本占用 45G 1亿文件需要占用5G
9亿文件的本地文件统计行数 90s
9亿文件拆分成24个文件(3800w单文件) 558s
3800w文件本地占用 2G
删除2G文本文件的开头一行 51s
删除2G文本文件的结尾一行 51s
删除2G文本文件的内的目录项 59s