Rsync 原理解析

当谈到Rsync时候,我们将使用一些术语来指代rsync工具在完成其任务的不同阶段 下的各个角色或者进程。下面为一些后文将会用到的术语:


client/客户端 role/角色 客户端对同步过程进行初始化。

server/服务器端 role/角色 服务器是指远端的rsync进程或者客户端通过远端shell、socket所连接到的系统。

服务器(server)是一个通用的术语,注意不要将其与Deamon混为一谈。

    一旦从Client到Server的链接建立起来,Client(客户 端)/Server(服务 器)的这两个角色的差别,就被Sender(发送者)/Receiver(接收者)所 取代了。

daemon/守护进程,同时也是进程 Daemon是一个rsync进程,该进程用于等待接收从Client发起的连接。在一 些平台上,Daemon也被叫做服务(Service)

 

remoteshell/远端shell 角色,同时也是一系列的进程 一个或多个进程,用于向client和远端的server之间提供连通性。

sender/发送者 role and process 可以存取待同步的文件资源的rsync进程。

receiver/接收者 role and process 作为角色:指同步过程中的目标系统;作为进程:指目标系统中,用于接收数据并接数据写入磁盘的进程。

generator/生产者 process/进程 生产者进程用于识别文件的变化,并维持文件级别的逻辑。

*/

 

当Rsync通过一个远端SHELL和一个没有启动守护程序的服务器通讯的时候,

Rsync所使用的启动方法是在远端系统上派生一个远端SHELL,然后使用这个远端SHELL启动一个Rsync进程.

Rsync的客户机和服务器通过远端SHELL的管道进行通讯.在这种模式下, Rsync服务器选项被传送给命令行,

用于启动远端SHELL. 

当Rsync和一个守护程序通讯的时候, 它直接和网络插口通讯. 这是唯一一种可以被称为涉及网络的的Rsync通讯. 在这种模式下, Rsync的选项必须发送到网络插口上. 下面是具体的描述.

开始通信:

客户机和服务器最开始通讯的时候, 他们各自发送自己所支持的最高的协议版本号给对方. 两边会使用其中的小的版本作为用来传输的协议版本.

如果是一个守护模式连接,Rsync的参数会被从客户机发送给服务器. 然后, 排出列表会被传送. 然后,客户机服务器的关系就只和错误和日志发送有关了.

   

    本地的Rsync任务(原地址和目标地址都是本地挂载的文件系统)就像一个推送.客户机:作为发送端, 派生一个服务器进程去行使服务器的功能. 客户机/发送端和服务器/接收端通过管道相互通讯.

   文件列表:文件列表不仅包括路径名,也包括所有者,模式, 读写权限, 大小和修改时间. 如果设置了--checksum选项, 文件列表还要包括文件的校验值.

   Rsync启动完成后的第一件事, 发送端会建立文件列表. 在建立过程中, 每个条目都会通过一种优化的网络传送方式发送给接收方.

   

    传输结束后, 两侧会以目录对基础目录的相关性来编排顺序. (具体的算法会和每次传输实用的协议版本有关). 一旦排序开始, 所以关于文件的指向都是使用他们在文件列表中的目录顺序.

如果必要, 发送者遵从文件列表中用户和组的id->name对应表 接收者会使用它来为文件列表中的每个文件作id->name->id翻译.

   

    接收端收到完全的文件列表, 会派生出一个生成器, 和接收端一起建立一个完整的管道.

管道  Rsync严重依赖於管道. 这意味着一组进程间的的单向通讯. 一旦文件列表被共享, 管道就表现为如下的形式,  生成器->发送端->接收端

   生成器的输出是发送端的输入, 发送端的输出是接收端的输入. 每个进程独立的运行, 只有在管道延迟,或者等待硬盘读写或CPU资源的时候才会有延迟.

生成器    生成器比较文件列表和本地目录树. 如果设置了--delete参数, 在开始它的主要工作前, 它首先会甄别在本地存在而在发送端上不存在的文件, 然后在接收端删除它们.

   

    接下来生成器会开始遍历文件列表. 每个文件都被检查, 以确定是否需要同步. 大多数情况下如果修改时间和大小不同, 文件需要同步.  如果设置了--checksum, 文件校验会被计算并比较. 目录, 设备文件和链结不会被跳过. 缺失的目录会被创建.

如果一个文件需要同步, 在接收端的任何版本的该文件都会被作为一个传输的"基础文件"."基础文件"作为一个数据源,两侧比较下来一致的数据就不需要被传输了. 为了更有效的在远端匹配数据, 基础文件的块校验被计算, 并和文件的目录号一起送给发送端.如果设置了--whole-file, 空的块校验值用于新文件.块大小, 以及在后期的版本中块校验的大小, 是基于每个文件的大小计算的.发送端  

 

发送端进程一次从生成器读一组文件号和相关联的块校验.

  

   对每一个生成器发送的文件号, 发送端会存储块校验, 并建立一个哈希索引以快速检索.接着本地文件会被读取, 生成一个从文件的第一个字节开始的块作的校验. 这个校验会和生成器发过来的校验比较, 如果不相符, "不匹配"的字节会被加入到不匹配的数据中, 接着比较下一个字节的块. 这被称为"循环校验"

 

   如果一个块的校验匹配就会被认为是一个匹配的块, 已经积累的不匹配块会被发送给接收端, 一起发送的还有块的偏移量和在接受端文件中的匹配块的长度. 块校验生成器会提前去检查匹配字节后面的一个字节.

 

即使块的顺序或者偏移量不同,以这种方法匹配的块也能够被确认. 这个程序是Rsync最核心的算法.通过这种方式, 发送者告诉接收端如何重组源文件成为一个目标文件. 这些指令包括所有的可以从基础文件拷贝的数据(如果存在的话), 和任何本地没有的新的数据, 的细节. 在处理末尾, 一个全文件的校验会被发送, 然后发送端去处理下一个文件.

  

   生成循环校验以及在校验中找到匹配的数据, 对CPU的能力有很大的需求. 在所有的Rsync进程中,发送端是最消耗CPU资源的.

接收端    接收端会从发送端的数据中读取由文件索引号确认的文件. 然后打开本地文件(被称为基础文件), 建立一个临时文件.

    接收端会读取非匹配数据和匹配数据, 并按顺序重组他们成为最终文件. 当非匹配数据被读取, 它会被写入到临时文件. 当收到一个块匹配记录, 接收端会寻找这个块在基础文件中的偏移量, 将这个块拷贝到临时文件. 通过这种方式, 临时文件被从头到尾建立起来.

   

    建立临时文件的时候生成了文件的校验. 重建文件结束后, 这个校验和来自发送端的校验比较. 如果校验不符, 临时文件会被删除. 如果失败一次, 文件会再被处理一次. 如果失败第二次, 一个错误会被报告.

临时文件建立后, 所有者, 权限和修改时间会被设置. 然后它会被重命名已替代基础文件.

  

    从基础文件拷贝数据到临时文件,使接收端成为所有进程中对硬盘要求最高的一个. 小文件还有可能在缓存中, 可以减轻对硬盘的压力; 但是对于大文件,在生成器去处理下一个文件的时候,或者还有由发送端造成的时延, 缓存中已经无法容纳更多的数据,只能清除掉旧的. 另外,数据是随机的从一个文件中读取, 并被写入另外一个, 如果读写的数据超过了硬盘缓存空间, 一个所谓的"寻找风暴"有可能发生,会进一步的损害性能.

守护程序    守护程序, 向所有的其他守护进程一样, 为每一个连接派生子进程. 启动的时候, 它解释rsyncd.conf, 以确认存在的模块, 并设置一些全局变量.

    当接收到一个对已经定义的模块的连接时, 守护进程派生一个子进程去处理这个连接. 这个子进程然后去读取rsyncd.conf,

为被请求的模块设置变量, 这个工作有可能改变模块的root路径, 或者抛弃已设定的用户号和组号. 然后, 它就像其他的Rsync服务进程一样,

或者作为发送端, 或者作为接收端.

Rsync协议   一个良好定义的通讯协议有以下几个特性,-所有的数据都在良好定义的包中发送, 包括包头, 可选的包体, 或者数据净荷.

-每个包头中, 明确的指定数据类型或者命令.

-每个包都有一个确定的长度.

    除了这些特性以外, 协议还应支持不同等级的状态, 包与包间的独立性, 人类可读性, 和重建一个断掉连接的能力.

   Rsyncs协议不具备任何以上一点优秀特性. 数据作为不间断的字节流被传输.不匹配的文件数据是一个特例,没有包长度, 没有计数器. 每个字节的意义都是根据协议等级决定的, 都是独立的.

    例如, 发送端要发送一个文件列表, 它就是简单的发送文件列表中的每一条, 发送结束就是一个NULL字节. 在文件列表的每一条中,有一个比特表示数据的结构, 这些变长的字符串只是被NULL字节简单的终结. 发送端发送文件索引号和块校验对的时候, 工作方式是一样的.

   

    在可靠的连接上, 这种方法工作的很好, 它比正式的协议拥有较少的开销. 但是很不幸, 它造成协议很难被文档化, 调试, 或者扩展. 每个版本的协议在线路上表现的都不同, 除非知道确切的版本号才可以参与.

 

后记

  文档还在继续被整理. 作者知道一定有一些明显的疏忽, 对于有些读者来说, 它更容易造成混乱, 而不是清晰. 希望它可以进化成一个有用的参考.欢迎提意见, 甚至重写的建议. 




Sync Algorithm: RSync vs. RDC

 

数据同步(Sync)是很多网络应用需要 的解决的问题,比如文件镜像。这里就以文件同步为例,问题模型:网络中两个主机Host-A和Host-B,都有同一文件File-Old的拷贝,现在这 个文件在Host-A上做了一些改变成为了File-New,需要通过同步让Host-B也获得F-New。
 
让我们想想怎么处理这个问题,最简单的方法,把所有数据都传输一遍,这样是简单,但是显得浪费,因为File-New相对于File-Old只 是有些小改变,全部copy代价太大。如果我们能够只传输发生改变的部分,也就是增、删、改的文件部分,那就太好了。这样,我们要解决的问题变成,如何得 到File-Old和File-New的差别。
 
如果Host-A上面保留有一个File-Old,那用普通的diff算法求一下和File-New的差别就行了,但是实际应用中,Host- A往往不会保留File-Old;或者文件格式本身有很强的版本控制功能,Host-B告诉Host-A它手上文件的版本,Host-A就能够计算出差 别;更多情况下,文件就是一串bytes,没有版本控制信息,没有历史拷贝,Rsync和RDC就是解决这种情况的同步的。
 
RSync算法是澳大利亚人 Andrew Tridgell发明的,我看懂 这个算法之后的第一感觉是:"嘿,这算法 我也应该能想出来!”的确,按照 Andrew Tridgell自己的话,这个 算法只需要半个小时就能够理解,但是花费 了他几年时间研究出来。
 
这里大概介绍一下Rsync算法大概原理:
1) Host-B把File-Old划分成不重合的大小为K字节的若干块,不足K字节的结尾部分加上Padding,然后对每一块求弱Hash和强Hash。 弱Hash就是说很有可能两个不同的块Hash值相同,但是计算起来快,而且这里要求这个若Hash能够Rolling,也就是说已知字节1到字节K这个 块的Hash值,能够很快的计算出字节2到字节K+1这个块的Hash值,往前Roll一个字节,计算很快;强Hash就是可以认为不同块肯定有不同 Hash值,Rsync用的是MD4。我们让WH表示弱Hash,SH表示强Hash。
2) Host-B把每个块的WH和SH值发送给Host-A。
3) 该Host-A上场了,他的运算量比较大。Host-A对File-New每一个长度为K的块(也就是以每个字节开头的长度为K的块)计算WH,计算出来 之后和Host-B发送过来的WH匹配,如果发现有相同的,再计算这个块的SH进行匹配,如果还是相符,说明这个块在File-Old里面也存在。假如 File-New长度为N,那么Host-A要处理大约(N-K)个块,这里可见用两个Hash算法的作用,WH用来做初步比较,而且因为它可以 Rolling,所以能够很快筛选掉大多数不匹配,对于漏网之鱼,也躲不过SH的筛选。
4) 通过上面的计算,Host-A可知道,File-New中哪些块和File-Old中的块相同,这样自然也可以计算出哪些不同,Host-A把这些不同 encode一下送给Host-B。
5) Host-B收到Host-A送来的数据,decode,就得到了File-New相对于File-Old的改变,于是获得了File-New。
 
整个过程只需要一个round-trip,而且可以精确的得到一个字节级别的差别,Host-A的运算量相对要大一些。
 
Rsync的实现已经是*inx上面的一个重要工具,所以,当Microsoft在Windows 2003 Server上推出DFSR(Distributed File System Replication)时,Open Source Community颇有 嘘 声。其实DFSR采用的是RDC(Remote Differential Compression)算法,和RSync相差很大,并没有抄袭RSync。
 
我感觉,RSync有学院气息(这个算法本来就是 Andrew Tridgell的博士论文), 结果很完美,File-New和 File-Old每一个字节的差别都计算出来了,但是Host-A和Host-B的计算量不对等,大部分的计算都集中在Host-A上。RDC和 RSync相比方向上有点不同,RDC并不追求计算出字节级别的diff,而是用较少的运算求出数据块级别的diff。
 
RDC算法要求Host-A和Host-B通过一致的规则对File-New和File-Old分别进行分块,然后对每个块计算 SH,Host-B把每个块的SH值发给Host-A,Host-A对两组SH进行diff,就可以知道有哪些块不同,哪些块被删掉了,哪些块被添加了。 RDC的关键在于分块规则,也使用WH,要让同一规则应用于File-Old和File-New的时候,分出来的块能够尽量体现出区别。
 
比如File-Old包含" I Love Playing Basketball”,
File-New是" I Like Playing Football"。
如果是RSync算法,Host-A能够计算出准确的差别," I Like Playing Football" 黄色部分修改了,绿色部分是增加的,精确到每个字符,Host-A主要告诉Host-B:"把第4-6号字符换成'ike',把16-21号字符去掉,插 入'Foot'”。
 
如果是RDC算法,可能得到下面的结果:
File-Old分块的结果,分成3块。
" I Love Playing Basketball
File-New分块的结果,分成3块。
" I Like Playing Football"
Host-A经过比对,发现只有File-Old的第2块和File-New的第2块匹配,于是就告诉Host-B:"把你的第一块换成‘I Like’,把你的第3块换成‘Football’”。
 
如上面看到,RDC相对而言比较浪费,相比RSync,要多传输一些数据,但是Host-A和Host-B的计算量比较平均。为了让RDC发挥 好的性能,一定要制定一个好的分块机制,让包含Diff的块尽量少包含没有Diff的数据,怎么做到这一点呢,还要靠WH,通过rolling checksum来从数据中快速挖掘出数据的性质。
 
注意一点就是RSync的分块策略是每块都是固定长度的,而RDC则每块长度可能不一样。
 
虽然RDC相对浪费一点,但是传送的大部分还是Delta数据,而且计算量相对平均而且较少,目前Window 2003 Server R2上的DFS使用的就是RDC算法,还有一个应用就是Live Messenger的Shared Folder功能,用一用,就知道效率不差了:)

Note:
本文前半部分翻译,原文可从rsync官方网站上得到,但是因为 时间原因,没有翻译完成,已翻译的部分也存在词不达意的现象,等以后有时间再修改吧。后半部分是转载的网友的文章,原文地址为 这里


引用的文章:

http://bbs.chinaunix.net/thread-2112273-1-1.html

http://andylin02.iteye.com/blog/1041752



你可能感兴趣的:(Linux)