本篇为rsync官方推荐技术报告rsync technical report的翻译,主要内容是Rsync的算法原理以及rsync实现这些原理的方法。翻译过程当中,在某些不易理解的地方加上了译者本人的注释。html
本人译做集合:http://www.cnblogs.com/f-ck-need-u/p/7048359.html算法
如下是rsync系列篇:
1.rsync(一):基本命令和用法
2.rsync(二):inotify+rsync详细说明和sersync
3.rsync算法原理和工做流程分析
4.rsync技术报告(翻译)
5.rsync工做机制(翻译)
6.man rsync翻译(rsync命令中文手册)测试
本报告介绍了一种将一台机器上的文件更新到和另外一台机器上的文件保持一致的算法。咱们假定两台机器之间经过低带宽、高延迟的双向链路进行通讯。该算法计算出源文件中和目标文件中一致的部分(译者注:数据块一致的部分),而后仅发送那些没法匹配(译者注:即两端文件中不一致的部分)的部分。实际上,该算法计算出了两个机器上两文件之间一系列的不一样之处。若是两文件类似,该算法的工做效率很是高,但即便两文件差异很是大,也能保证正确且有必定效率的工做。编码
假设你有两个文件A和B,你但愿更新B让它和A彻底相同,最直接的方法是拷贝A变为B。spa
但想象一下,若是这两个文件所在机器之间以极慢的通讯链路进行链接通讯,例如使用拨号的IP链路。若是A文件很大,拷贝A到B速度是很是慢的。为了提升速度,你能够将A压缩后发送,但这种方法通常只能得到20%到40%的提高。翻译
如今假定A和B文件很是类似,也许它们二者都是从同一个源文件中分离出来的。为了真正的提升速度,你须要利用这种类似性。一个通用的方法是经过链路仅发送A和B之间差别的部分,而后根据差别列表重组文件(译者注:因此还须要建立差别列表,发送差别列表,最后匹配差别列表并重组)。设计
这种通用方法的问题是,要建立两个文件之间的差别集合,它依赖于有打开并读取两文件的能力。所以,这种方法在读取两文件以前,要求事先从链路的另外一端获取文件。若是没法从另外一端获取文件,这种算法就会失效(但若是真的从另外一端获取了文件,两文件将同在一台机器上,此时直接拷贝便可,没有必要比较这些文件的差别)。rsync就是处理这种问题的。3d
rsync算法能高效率地计算出源文件和目标已存在文件相同的部分(译者注:即能匹配出相同的数据块)。这些相同的部分不须要经过链路发送出去;全部须要作的是引用目标文件中这些相同的部分来作匹配参照物(译者注:即基准文件basis file)。只有源文件中不能匹配上的部分才会以纯数据的方式被逐字节发送出去。以后,接收端就能够经过这些已存在的相同部分和接收过来的逐字节数据组建成一个源文件的副本。htm
通常来讲,发送到接收端的数据可使用任意一种常见的压缩算法进行压缩后传输,以进一步提升速度。blog
假设咱们有两台计算机α和β,α上有能够访问的文件A,β上有能够访问的文件B,且A和B两文件是类似的。α和β之间以低速链路通讯。
rsync算法由如下过程组成:
1.β将文件B分割为一系列不重叠且大小固定为S字节(译者注:做者们备注说500到1000字节比较适合)的数据块。固然,最后一个数据块可能小于S字节。
2.β对每一个这样的数据块都计算出两个校验码:32位的弱校验码rolling-checksum和128位的强校验码MD4-checksum(译者注:如今的rsync使用的是128位的MD5-checksum)。
3.β将这些校验码发送给α。
4.α将搜索文件A,从中查找出全部长度为S字节且和B中两个校验码相同的数据块(从任意偏移量搜索)。这个搜索和比较的过程能够经过使用弱滚动校验(rolling checksum)的特殊功能很是快速地完成。
(译者注:以字符串123456为例,要搜索出包含3个字符的字符串,若是以任意偏移量的方式搜索全部3个字符长度的字符串,最基本方法是从1开始搜索获得123,从2开始搜索获得234,从3开始搜索获得345,直到搜索完成。这就是任意偏移量的意思,即从任意位置搜索长度为S的数据块)
(译者再注:之因此要以任意偏移量搜索,考虑一种状况,现有两个彻底相同的文件A和B,如今向A文件的中间插入一段数据,那么A中从这段数据开始,紧跟其后的全部数据块的偏移量都向后挪动了一段长度,若是不以任意偏移量搜索固定长度数据块,那么重新插入的这段数据开始,全部的数据块都和B不同,在rsync中意味着这些数据块都要传输,但实际上A和B不一样的数据块只有插入在中间的那一段而已)
5.α将一系列的指令发送给β以使其构造出A文件的副本。每一个指令要么是对B中数据块的引用,要么是纯数据。这些纯数据是A中没法匹配到B中数据块的数据块部分(译者注:就是A中不一样于B的数据块)。
最终的结果是β获取到了A的副本,但却仅发送了A中存在B中不存在的数据部分(还包括一些校验码以及数据块索引数据)。该算法也仅只要求一次链路往返(译者注:第一次链路通讯是β发送校验码给α,第二次通讯是α发送指令、校验码、索引和A中存在B中不存在的纯数据给β),这能够最小化链路延迟致使的影响。
该算法最重要的细节是滚动校验(rolling checksum)以及与之相关的多备选(multi-alternate)搜索机制,它们保证了全部偏移校验(all-offsets checksum,即上文步骤4中从任意偏移位置搜索数据块)的搜索过程能够处理的很是迅速。下文将更详细地讨论它们。
(译者注:若是不想看算法理论,下面的算法具体内容能够跳过不看(能够看下最后的结论),只要搞懂上面rsync算法过程当中作了什么事就够了。)
rsync算法使用的弱滚动校验(rolling checksum)须要可以快速、低消耗地根据给定的缓冲区X1 ...Xn 的校验值以及X1、Xn+1的字节值计算出缓冲区的校验值。
咱们在rsync中使用的弱校验算法设计灵感来自Mark Adler的adler-32校验。咱们的校验定义公式为:
其中s(k,l)是字节的滚动校验码。为了简单快速地计算出滚动校验码,咱们使用
。
此校验算法最重要的特性是能使用递归关系很是高效地计算出连续的值。
所以能够为文件中任意偏移位置的S长度的数据块计算出校验码,且计算次数很是少。
尽管该算法足够简单,但它已经足够做为两个文件数据块匹配时的第一层检查。咱们在实践中发现,当数据块内容不一样时,校验码(rolling checksum)能匹配上的几率是很低的。这一点很是重要,由于每一个弱校验能匹配上的数据块都必须去计算强校验码,而计算强校验码是很是昂贵的。(译者注:也就是说,数据块内容不一样,弱校验码仍是可能会相同(尽管几率很低),也就是rolling checksum出现了碰撞,因此还要对弱校验码相同的数据块计算强校验码以作进一步匹配)
当α收到了B文件数据块的校验码列表,它必需要去搜索A文件的数据块(以任意偏移量搜索),目的是找出能匹配B数据块校验码的数据块部分。基本的策略是从A文件的每一个字节开始依次计算长度为S字节的数据块的32位弱滚动校验码(rolling checksum),而后对每个计算出来的弱校验码,都拿去和B文件校验码列表中的校验码进行匹配。在咱们实现的算法上,使用了简单的3层搜索检查(译者注:3步搜索过程)。
第一层检查,对每一个32位弱滚动校验码都计算出一个16位长度的hash值,并将每216个这样的hash条目组成一张hash表。根据这个16位hash值对校验码列表(例如接收到的B文件数据块的校验码集合)进行排序。hash表中的每一项都指向校验码列表中对应hash值的第一个元素(译者注:即数据块ID),或者当校验码列表中没有对应的hash值时,此hash表项将是一个空值(译者注:之因此有空校验码以及对应空hash值出现的可能,是由于β会将那些α上有而β上没有的文件的校验码设置为空并一同发送给α,这样α在搜索文件时就会知道β上没有该文件,而后直接将此文件整个发送给β)。
对文件中的每一个偏移量,都会计算它的32位滚动校验码和它的16位hash值。若是该hash值的hash表项是一个非空值,将调用第二层检查。
(译者注:也就是说,第一层检查是比较匹配16位的hash值,能匹配上则认为该数据块有潜在相同的可能,而后今后数据块的位置处进入第二层检查)
第二层检查会对已排序的校验码列表进行扫描,它将从hash表项指向的条目处(译者注:此条目对应第一层检查结束后能匹配的数据块)开始扫描,目的是查找出能匹配当前值的32位滚动校验码。当扫描到相同32位弱滚动校验值时或者直到出现不一样16位hash值都没有匹配的32位弱校验码时,扫描终止(译者注:因为hash值和弱校验码重复的几率很低,因此基本上向下再扫描1项最多2项就能发现没法匹配的弱滚动校验码)。若是搜索到了能匹配的结果,则调用第三层检查。
(译者注:也就是说,第二层检查是比较匹配32位的弱滚动校验码,能匹配上则表示仍是有潜在相同的可能性,而后今后位置处开始进入第三层检查,若没有匹配的弱滚动校验码,则说明不一样数据块内容的hash值出现了重复,但好在弱滚动校验的匹配将其排除掉了)
第三层检查会对文件中当前偏移量的数据块计算强校验码,并将其与校验码列表中的强校验码进行比较。若是两个强校验码能匹配上,咱们认为A中的数据块和B中的数据块彻底相同。理论上,这些数据块仍是有可能会不一样,可是几率是极其微小的,所以在实践过程当中,咱们认为这是一个合理的假设。
当发现了能匹配上的数据块,α会将A文件中此数据块的偏移量和前一个匹配数据块的结束偏移地址发送给β,还会发送这段匹配数据块在B中数据块的索引(译者注:即数据块的ID,chunk号码)。当发现能匹配的数据时,这些数据(译者注:包括匹配上的数据块相关的重组指令以及处于两个匹配块中间未被匹配的数据块的纯数据)会当即被发送,这使得咱们能够将通讯与进一步的计算并行执行。
若是发现文件中当前偏移量的数据块没有匹配上时,弱校验码将向下滚动到下一个偏移地址而且继续开始搜索(译者注:也就是说向下滚动了一个字节)。若是发现能匹配的值时,弱校验码搜索将从匹配到的数据块的终止偏移地址从新开始(译者注:也就是说向下滚动了一个数据块)。对于两个几乎一致的文件(这是最多见的状况),这种策略节省了大量的计算量。另外,对于最多见的状况,A的一部分数据能依次匹配上B的一系列数据块,这时对数据块索引号进行编码将是一件很简单的事。
上面几个小章节描述了在远程系统上组建一个文件副本的过程。若是咱们要拷贝一系列文件,咱们能够将过程流水线化(pipelining the process)以期得到很可观的延迟上的优点。
这要求β上启动的两个独立的进程。其中一个进程负责生成和发送校验码给α,另外一个进程则负责从α接收不一样的信息数据以便重组文件副本。(译者注:即generator-->sender-->receiver)
若是链路上的通讯是被缓冲的,两个进程能够相互独立地不断向前工做,而且大多数时间内,能够保持链路在双方向上被充分利用。
为了测试该算法,建立了两个不一样Linux内核版本的源码文件的tar包。这两个内核版本分别是1.99.10和2.0.0。这个tar包大约有24M且5个不一样版本的补丁分隔。
在1.99.10版本中的2441个文件中,2.0.0版本中对其中的291个文件作了更改,并删除了19个文件,以及添加了25个新文件。
使用标准GNU diff程序对这两个tar包进行"diff"操做,结果产生了超过32000行总共2.1 MB的输出。
下表显示了两个文件间使用不一样数据块大小的rsync的结果。
block size |
matches |
tag hits |
false alarms |
data |
written |
read |
300 |
64247 |
3817434 |
948 |
5312200 |
5629158 |
1632284 |
500 |
46989 |
620013 |
64 |
1091900 |
1283906 |
979384 |
700 |
33255 |
571970 |
22 |
1307800 |
1444346 |
699564 |
900 |
25686 |
525058 |
24 |
1469500 |
1575438 |
544124 |
1100 |
20848 |
496844 |
21 |
1654500 |
1740838 |
445204 |
在每种状况下,所占用的CPU时间都比在两个文件间直接运行"diff"所需时间少。
表中各列的意思是:
block size:计算校验和的数据块大小,单位字节。
matches:从A中匹配出B中的某个数据块所匹配的次数。(译者注:相比于下面的false matches,这个是true matches)
tag hits:A文件中的16位hash值能匹配到B的hash表项所需的匹配次数。
false alarms:32位弱滚动校验码能匹配但强校验码不能匹配的匹配次数。(译者注:即不一样数据块的rolling checksum出现小几率假重复的次数)
data:逐字节传输的文件纯数据,单位字节。
written:α所写入的总字节数,包括协议开销。这几乎全是文件中的数据。
read:α所读取的总字节数,包括协议开销,这几乎全是校验码信息数据。
结果代表,当块大小大于300字节时,仅传输了文件的一小部分(大约5%)数据。并且,相比使用diff/patch方法更新远端文件,rsync所传输的数据也要少不少。
每一个校验码对占用20个字节:弱滚动校验码占用4字节,128位的MD4校验码占用16字节。所以,那些校验码自己也占用了很多空间,尽管相比于传输的纯数据大小而言,它们要小的多。
false alarms少于true matches次数的1/1000,这说明32位滚动校验码能够很好地检测出不一样数据块。
tag hits的数值代表第二层校验码搜索检查算法大体每50个字符被调用一次(译者注:以block size=1100那行数据为例,50W*50/1024/1024=23M)。这已经很是高了,由于在这个文件中全部数据块的数量是很是大的,所以hash表也是很是大的。文件越小,咱们指望tag hit的比例越接近于匹配次数。对于极端状况的超大文件,咱们应该合理地增大hash表的大小。
下一张表显示了更小文件的结果。这种状况下,众多文件并无被归档到一个tar包中,而是经过指定选项使得rsync向下递归整个目录树。这些文件是以另外一个称为Samba的软件包为源抽取出来的两个版本,源码大小为1.7M,对这两个版本的文件运行diff程序,结果输出4155行共120kB大小。
block size |
matches |
tag hits |
false alarms |
data |
written |
read |
300 |
3727 |
3899 |
0 |
129775 |
153999 |
83948 |
500 |
2158 |
2325 |
0 |
171574 |
189330 |
50908 |
700 |
1517 |
1649 |
0 |
195024 |
210144 |
36828 |
900 |
1156 |
1281 |
0 |
222847 |
236471 |
29048 |
1100 |
921 |
1049 |
0 |
250073 |
262725 |
23988 |