rsync指令的使用与算法解析
简介
rsync命令是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。rsync使用所谓的rsync算法来使本地和远程两个主机之间的文件达到同步,这个算法只传送两个文件的不同部分,而不是每次都整份传送,因此速度相当快。 rsync是一个功能非常强大的工具,其命令也有很多功能特色选项,我们下面就对它的选项一一进行分析说明。
举个例子
rsync支持文件增量同步。可以用于本地文件复制,也可以把本地文件复制到远程,或从远程复制文件到本地。(但不支持两台远程计算机之间的同步)
参数解析
-v, --verbose 详细模式输出。
-q, --quiet 精简输出模式。
-c, --checksum 打开校验开关,强制对文件传输进行校验。
-a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于-rlptgoD。
-r, --recursive 对子目录以递归模式处理。
-R, --relative 使用相对路径信息。
-b, --backup 创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用--suffix选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename)存放在在目录下。
-suffix=SUFFIX 定义备份文件前缀。
-u, --update 仅仅进行更新,也就是跳过所有已经存在于DST,并且文件时间晚于要备份的文件,不覆盖更新的文件。
-l, --links 保留软链结。
-L, --copy-links 想对待常规文件一样处理软链结。
--copy-unsafe-links 仅仅拷贝指向SRC路径目录树以外的链结。
--safe-links 忽略指向SRC路径目录树以外的链结。
-H, --hard-links 保留硬链结。
-p, --perms 保持文件权限。
-o, --owner 保持文件属主信息。
-g, --group 保持文件属组信息。
-D, --devices 保持设备文件信息。
-t, --times 保持文件时间信息。
-S, --sparse 对稀疏文件进行特殊处理以节省DST的空间。
-n, --dry-run现实哪些文件将被传输。
-w, --whole-file 拷贝文件,不进行增量检测。
-x, --one-file-system 不要跨越文件系统边界。
-B, --block-size=SIZE 检验算法使用的块尺寸,默认是700字节。
-e, --rsh=command 指定使用rsh、ssh方式进行数据同步。
--rsync-path=PATH 指定远程服务器上的rsync命令所在路径信息。
-C, --cvs-exclude 使用和CVS一样的方法自动忽略文件,用来排除那些不希望传输的文件。
--existing 仅仅更新那些已经存在于DST的文件,而不备份那些新创建的文件。
--delete 删除那些DST中SRC没有的文件。
--delete-excluded 同样删除接收端那些被该选项指定排除的文件。
--delete-after 传输结束以后再删除。
--ignore-errors 及时出现IO错误也进行删除。
--max-delete=NUM 最多删除NUM个文件。
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输。
--force 强制删除目录,即使不为空。
--numeric-ids 不将数字的用户和组id匹配为用户名和组名。
--timeout=time ip超时时间,单位为秒。
-I, --ignore-times 不跳过那些有同样的时间和长度的文件。
--size-only 当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间。
--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为0。
-T --temp-dir=DIR 在DIR中创建临时文件。
--compare-dest=DIR 同样比较DIR中的文件来决定是否需要备份。
-P 等同于 --partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输。
--progress 显示备份过程。
-z, --compress 对备份的文件在传输时进行压缩处理。
--exclude=PATTERN 指定排除不需要传输的文件模式。
--include=PATTERN 指定不排除而需要传输的文件模式。
--exclude-from=FILE 排除FILE中指定模式的文件。
--include-from=FILE 不排除FILE指定模式匹配的文件。
--version 打印版本信息。
--address 绑定到特定的地址。
--config=FILE 指定其他的配置文件,不使用默认的rsyncd.conf文件。
--port=PORT 指定其他的rsync服务端口。
--blocking-io 对远程shell使用阻塞IO。
-stats 给出某些文件的传输状态。
--progress 在传输时现实传输过程。
--log-format=formAT 指定日志文件格式。
--password-file=FILE 从FILE中得到密码,格式为文件中单行写入密码
--bwlimit=KBPS 限制I/O带宽,KBytes per second。
-h, --help 显示帮助信息。
工作模式
rsync有三种不同的工作模式,详细介绍如下:
本地模式 - 拷贝本地文件
- 规则:当SRC和DES路径信息都不包含有单个冒号:分隔符时就启动该模式;
- 语法:rsync [OPTION]... SRC DEST;
- 示例``:rsync -a /data /backup`;
rsync -aP --delete --checksum --exclude='*.txt' src dest
远程shell模式
将本地机器的内容拷贝到远程机器
- 规则:当DST路径地址包含单个冒号:分隔符时启动该模式(ssh协议);
- 语法:rsync [OPTION]... SRC [USER@]host:DEST;
- 示例:rsync -avz *.c foo:src
rsync -aPv --delete --checksum rsyncfr user@host:/dir #默认使用SSH协议进行远程登录和数据传输
rsync -aPv -e 'ssh -p 2222' src user@remote_host:/dest # ssh端口为2222
将远程机器的内容拷贝到本地机器
- 规则:当SRC地址路径包含单个冒号:分隔符时启动该模式;
- 语法:rsync [OPTION]... [USER@]HOST:SRC DEST;
- 示例:rsync -avz foo:src/bar /data;
rsync -aPv --delete --checksum user@host:/home/user/test/rsyncfr rsyncto/
rsync -aPv --delete --checksum -e 'ssh -p 2222' user@host:/home/user/test/rsyncfr rsyncto/
远程rsync服务器模式
从远程rsync服务器中拷贝文件到本地机
- 规则:当SRC路径信息包含::分隔符时启动该模式;
- 语法:rsync [OPTION]... [USER@]HOST::SRC DEST;
- 示例:rsync -av root@host::www /databack;
rsync -aPv username@host::module/dest src
rsync -aPv rsync://host/module/dest src
从本地机器拷贝文件到远程rsync服务器
- 规则:当DST路径信息包含::分隔符时启动该模式;
- 语法:rsync [OPTION]... SRC [USER@]HOST::DEST;
- 示例:rsync -av /databack root@host::www;
rsync -aPv src username@host::module/dest
rsync -aPv src rsync://host/module/dest
# 将文件同步到远程rsync服务器,但是相应父路径不存在时
rsync -aPv --relative /a/b/c/./x/y/z/ host::module/dest
列出远程机的文件列表
- 规则:命令中省略掉本地机信息;
- 语法:rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST];
- 示例:rsync -v rsync://host/www;
rsync server使用
服务启动
- rsync server daemon: rsync server以daemon的方式存在,自己accept请求,然后fork子进程使用tcp socket进行通信。
- rsync over ssh: 使用sshd进程的stdin、stdout进行通信,一次同步完成会结束。
- rsync start by inetd/xinetd: 由inetd/xinetd来accept请求,然后fork rsync进程去处理并将socket fd重定向到stdin/stdout,一次同步完成会结束。
工作流程与原理
基础组件(一个名称为rsync的二进制,根据参数可以fork三个主要的进程)
- sender:指同步过程中的源路径的rsync进程,根据generator提供的文件列表信息识别变化的块并将块传递给receiver。
- receiver:指同步过程中的目标路径的rsync进程之一,主要用于接收文件数据并将数据写入磁盘并进行校验。
- generator:指同步过程中的目标路径的rsync进程之一,用于识别文件的变化,生成需要同步的文件列表信息以及checksum等发送给sender。
| ---- sender <-------- |
|---|
| -> receiver -> generator |
generator和receiver之间通过pipe或socket pair进行通信,sender与generator使用tcp socket或ssh通信,receiver与generator使用tcp socket或ssh通信 generator与receiver在同一机器上,通过pipe或socket pair进行通信 generator的输出作为sender的输入,使用tcp socket或ssh进行通信 sender的输出作为receiver的输入,使用tcp socket或ssh进行通信 receiver的输出作为generator的输入,通过pipe或socket pair进行通信
基础流程
- 如rsync -aPv username@host::module/src dest,从host::module/src同步文件到dest,此时建立连接,本地作为receiver端,远端作为sender端。sender端会fork出sender进程提供相关服务,receiver端进行一些处理后会fork出两个进程,一个是generator,另一个是receiver。
- 连接建立成功会时,sender端的sender进程根据rsync命令行中给出的源路径收集待同步的文件列表,包含文件的一些基本属性,如uid、gid、权限、大小、创建时间、修改时间等,当然与具体参数有关,如果指定了--checksum,还会含每个文件的checksum,如果指定了--exclude还会剔除掉相关路径。会批量的将这些文件列表发送给receiver端。
- receiver端接收到sender发送的文件列表后,会fork出receiver子进程,原进程会切换成generator身份,generator会根据文件列表扫描本地文件,如果rsync指定了--delete,会先删除源路径中没有但本地存在的文件,如果文件列表和本地都存在,会使用quick_check_ok去判断文件的时间和大小是否变化,但若rsync指定了--cchecksum, 还会计算这个文件的摘要,判断内容是否真的改变。如果文件发生改变则表示此文件需要同步,此时generator会对文件进程分块并编号,通过generate_and_send_sums计算并发送所有的分块checksum给sender。
- sender进程收到generator发送的数据,会读取文件编号和校验码列表,并将校验码列表中的滚动校验码(rolling checksum)计算hash,相当于以checksum为键,块编号为值保存到hash表。并根据本地文件快速计算出不匹配的块,并将这些匹配和不匹配的块信息以及整个文件的摘要发送给receiver端。注:此处是rsync的亮点,即rolling checksum,核心方法是hash_search。
- receiver接收到数据后会创建临时文件进行文件重组,如果此块在本地文件存在则重用,不存在则请求sender发送过来,待重组完成会计算整个临时文件的摘要,并与先前收到的摘要进行对比,如果不匹配则会重新完全重组。
- receiver重组文件成功后会修改该临时文件的属性信息,包括mode、uid、gid、创建和修改时间等。然后重命名并覆盖掉目标文件。至此,文件同步完成。
rsync同步文件检查模式
默认模式
使用"quick check"算法快速检查源文件和目标文件的大小、mtime(修改时间)是否一致,如果不一致则需要传输。
- "--size-only"选项表示"quick check"将仅检查文件大小不同的文件作为待传输文件
检查算法开启模式&核心算法
--checksum
分块checksum算法
首先,我们会把DST文件的文件均切分成若干小块,例如每块大小为512个字节(最后一块会小于这个数),然后对每块计算两个checksum,计算checksum使用的算法如下:
- rolling-checksum,是弱checksum,32位的checksum,其使用的是Mark Adler发明的adler-32算法;
- 强checksum,128位的,以前用md4,现在用md5 hash算法。
传输算法
同步目标端会把fileDst的一个checksum列表传给同步源,这个列表里包括了三个东西,rolling checksum(32bits),md5 checksume(128bits),文件块编号。
我估计你猜到了同步源机器拿到了这个列表后,会对fileSrc做同样的checksum,然后和fileDst的checksum做对比,这样就知道哪些文件块改变了。
问题:
如果fileSrc这边在文件中间加了一个字符,这样后面的文件块都会位移一个字符,这样就完全和fileDst这边的不一样了,但理论上来说,应该只需要传一个字符就好了。这个怎么解决?
如果这个checksum列表特别长,而两边的相同的文件块可能并不是一样的顺序,那就需要查找,线性的查找起来应该特别慢吧。这个怎么解决?
checksum查找算法
同步源端拿到fileDst的checksum数组后,会把这个数据存到一个hash table中,用rolling checksum做hash,以便获得O(1)时间复杂度的查找性能。这个hash table是16bits的,所以,hash table的尺寸是2的16次方,对rolling checksum的hash会被散列到0 到 2^16 1中的某个整数值。
比对算法 - 这是最关键的算法
- 1)取fileSrc的第一个文件块(我们假设的是512个长度),也就是从fileSrc的第1个字节到第512个字节,取出来后做rolling checksum计算。计算好的值到hash表中查。
- 2)如果查到了,说明发现在fileDst中有潜在相同的文件块,于是就再比较md5的checksum,因为rolling checksume太弱了,可能发生碰撞。于是还要算md5的128bits的checksum,这样一来,我们就有 2^-(32+128) = 2^-160的概率发生碰撞,这太小了可以忽略。如果rolling checksum和md5 checksum都相同,这说明在fileDst中有相同的块,我们需要记下这一块在fileDst下的文件编号。
- 3)如果fileSrc的rolling checksum 没有在hash table中找到,那就不用算md5 checksum了。表示这一块中有不同的信息。总之,只要rolling checksum 或 md5 checksum 其中有一个在fileDst的checksum hash表中找不到匹配项,那么就会触发算法对fileSrc的rolling动作。于是,算法会往后step 1个字节,取fileSrc中字节2-513的文件块要做checksum,go to (4.1) 现在你明白什么叫rolling checksum了吧。
- 4)这样,我们就可以找出fileSrc相邻两次匹配中的那些文本字符,这些就是我们要往同步目标端传的文件内容了。
rolling checksum算法
也叫Rabin-Karp 算法,由 Richard M. Karp 和 Michael O. Rabin 在 1987 年发表,它也是用来解决多模式串匹配问题的。其最大的精髓是,当我们往后面step 1个字符的时候,不用全部重新计算所有的checksum,也就是说,我们从 [0, 512] rolling 到 [1, 513] 时,我们不需要重新计算从1到513的checksum,而是重用 [0,512]的checksum直接算出来。
举个栗子 我们有一个数字:12345678,假设我们以5个长度作为一个块,那么,第一个块就是 12345 ,12345可以表示为:
1 * 10^4 + 2 * 10^3 + 3 * 10^2 + 4 * 10^1 + 5 * 10^0 = 12345
如果我们要step 1步,也就是要得到 23456, 我们不必计算:
2 * 10^4 + 3 * 10^3 + 4 * 10^2 + 5 * 10^1 + 6 * 10^0
而是直接计算:
(12345 - 1 * 10^4) * 10 + 6 * 10 ^0
我们可以看到,其中,我们把12345最左边第一位去掉,然后,再加上最右边的一位。这就是Rolling checksum的算法。
实际的公式是:
hash ( t[0, m-1] ) = t[0] * b^(m-1) + t[1] * b^[m-2] ..... t[m-1] * b^0
其中的 b是一个常数基数,在 Rabin-Karp 算法中,我们一般取值为 256。
于是,在计算 hash ( t[1, m] ) 时,只需要下面这样就可以了:
hash( t[1, m] ) = hash ( t[0, m-1] ) - t[0] * b^(m-1) + t[m] * b ^0
图解
这样,在同步源这端,我们的rsync算法可能会得到下面这样子的一个数据数组,图中,红色块表示在目标端已匹配上,不用传输(注:我专门在其中显示了两块chunk #5,相信你会懂的),而白色的地方就是需要传输的内容(注意:这些白色的块是不定长的),这样,同步源这端把这个数组(白色的就是实际内容,红色的就放一个标号)压缩传到目的端,在目的端的rsync会根据这个表重新生成文件,这样,同步完成。
最后想说一下,对于某些压缩文件使用rsync传输可能会传得更多,因为被压缩后的文件可能会非常的不同。对此,对于gzip和bzip2这样的命令,记得开启 “rsyncable” 模式。
思考
- 512只是一个例子,并不是算法就设置成这样的。这个是可以通过参数输入的。另,rsync的网络传输有-z参数,压缩后再传。
- 取512字节大小作为运算单位,估计是考虑到文件对齐的原因。磁盘扇区原先都是512字节大小。现在倒是有扇区4K大小的磁盘了.总之大小对齐是必须的.
- 等等