正在研究Rsync, 在Rsync的主页上看到了这篇对Rsync解释的不那么枯燥的文章. 尝试着翻译了一下. 原文虽然在大方向上清晰的解释了Rsync的工作方式, 但在具体的细节的时候还是觉的很晦涩. 在有些地方, 完全去我自己理解后的"意译"了. 对于这篇文章, 也许重写是个更好的选择.
这是我第一次翻译技术文档, 疏漏难免, 欢迎斧正.
原文URL, , 不知作者是谁.
How Rsync Works
A Practical Overview
从实际应用的角度看Rsync是如何工作的
前言
对于理解Rsync的算法理论和机制, 最初的RsyncTechnicalReport和AndrewTridgell的博士论文都是非常优秀的文档. 不幸的是, 他们都太过于理论, 而缺乏Rsync实践应用.
在这份文档中我希望能够描述
-一份非数学的Rsync算法综述
-这个算法在Rsync程序中是如何实现的
-Rsync使用的协议
-Rsync各个进程的一个可定义的角色
这份文档对于需要更多信息的编程者可以作为一份指导手册, 但是最主要的作用是从读者能够理解的角度给他们一个以下几方面的基础知识,
-为什么Rsync是这样表现的
-Rsync的限制
-为什么有些功能请求对于目前的程序是不合适的.
这篇文档泛泛的描述了Rsync的构成和行为. 在有些情况下, 为了满足一个更广泛的目标, 有些可以使文档更准确的细节和特例被牺牲掉了.
进程和他们所扮演的角色
当我们谈论Rsync的时候, 我们使用了特定的名词来代表Rsync执行过程中不同的进程和他们所扮演的角色. 为了更好的沟通, 我们使用相同的语言是很重要的. 我们在使用某一特定的名词的时候指的是同一件事情. 在Rsync邮件列表上, 总有关于进程和角色的疑惑. 出于这个原因我将定义几个名词, 接下来我会用他们描述进程和相应的角色.
客户机 角色 初始化同步任务的一方.
服务器 角色 指客户机通过本地传输, 或者远端SHELL, 或者网络插口, 连接到 远端的Rsync进程或者系统. 这是一个通用的名词, 不应该和守护 程序混淆. 一旦客户机和服务器的连接建立, 这两个名词会被发送 端和接收端替代.
守护程序 角色和进程 守护程序指等待客户机连接的一个Rsync进程. 在某些平台上, 它被称 为一项服务.
远端SHELL 角色和一组进程 一个或者多个进程, 能够提供客户机和远端的服务器的连接.
发送端 角色和进程 能够访问需要同步的源文件的Rsync进程.
接收端 角色和进程 作为一种功用, 接收端是一个目标系统. 作为一个进程, 接收端接受 需要更新的数据并把它们写到磁盘上.
生成器 进程 生成器进程确认改变了的文件, 并管理文件等级逻辑.
进程的启动
客户机启动后的第一件事是和服务器建立连接. 这个连接可以通过管道或者通过网络插口.
当Rsync 通过一个远端SHELL和一个没有启动守护程序的服务器通讯的时候, Rsync所使用的启动方法是在远端系统上派生一个远端SHELL,然后使用这个远端SHELL启动一个Rsync进程. Rsync的客户机和服务器通过远端SHELL的管道进行通讯. (As far as the rsync processes concerned there is no network. --不知道怎么翻译) 在这种模式下, 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字节简单的终结. 发送端发送文件索引号和块校验对的时候, 工作方式是一样的.
在可靠的连接上, 这种方法工作的很好, 它比正式的协议拥有较少的开销. 但是很不幸, 它造成协议很难被文档化, 调试, 或者扩展. 每个版本的协议在线路上表现的都不同, 除非知道确切的版本号才可以参与.
后记
文档还在继续被整理. 作者知道一定有一些明显的疏忽, 对于有些读者来说, 它更容易造成混乱, 而不是清晰. 希望它可以进化成一个有用的参考.欢迎提意见, 甚至重写的建议.