Remote Sync,1996 年 由 Andrew Tridgell 和 Paul Mackerras 编写。几乎所有发行版都内置了该工具。

rsync 简介

rsync 是可以实现增量备份的工具。配合任务计划,rsync 能实现定时或间隔同步,配合 inotify 或 sersync,可以实现 触发式的实时同步

  • 可用于 本地 复制及远程复制,不支持两个远程主机间的同步。
  • 提供了大量的参数来控制其行为的每个要素,可以对要复制的文件进行非常灵活的设定。
  • delta-transfer 机制 比较著名,该机制通过 仅传输两端文件的差异部分,而减少了网络传输的数据量。
  • 广泛应用于备份及镜像,同时还是一个功能强大的复制工具。
  • 默认 使用一种 快速检查 机制来确定需传输的文件。

rsync 特点

  • 支持复制链接、设备、所有者、组、权限
  • 支持排除特定文件,用法与 GNU tar 相似
  • 支持 CVS 模式的排除
  • 支持使用任何透明的远程 shell ,包括 ssh 或 rsh
  • 无需超级用户身份就可使用
  • 通过管道传输文件,减小延迟
  • 支持匿名或授权同步

delta-transfer 机制

rsync 使用一种 delta-transfer 机制。首先会假设两端主机使用一个低带宽、高延迟、双向通信的连接,该机制会把源文件中与目标文件相同的部分标记出来,然后 只传输不同的部分。该机制能有效地计算出双方文件的不同之处,尤其适合于两端文件很相似的场合。

该机制有效地减少了同步时需要传输的数据量。

rsync 的增量传输

发起连接的一端称为 客户端,即执行 rsync 命令的一端,另一端称为 服务端

假设待传输文件为 A,如果目标路径中没有该文件,则 rsync 会直接传输,如果已存在,则视情况决定是否要传输。

rsync 默认使用快速检查算法(quick check),它比较源文件和目标文件的 文件大小mtime,如果不同,则传输该文件,否则忽略。

当发现两端文件内容没变,只是 属性 发生变化时,只在服务端对文件属性进行更新。

如果决定要传输文件 A,不会传输整个文件,而是 只传 源文件和目标文件之间 不同的部分,即 增量传输

rsync 同步基本说明

rsync 的 目的 是实现 文件同步,因此涉及 源文件目标文件、以及 同步基准 的概念。

同步过程中还会涉及到源与目标之间的 版本控制 问题。

例如,是否要删除源主机上没有但目标上多出来的文件,目标文件比源文件更新 (newer than source) 时是否仍要保持同步,遇到软链接时是拷贝软链接本身还是拷贝软链接所指向的文件,目标文件已存在时是否要先对其做个备份等等。

rsync 工作方式

在使用 rsync 命令时,需指定 源路径目标路径 参数。

  • 第一个 路径参数一定是 源文件路径,即 同步基准
  • 最后一个 路径参数一定是 目标文件路径,即 待同步方

本地文件系统同步

本质是 通过管道通信

rsync [OPTION...] SRC... [DEST]

路径格式为本地路径。

使用远程 shell 连接

本质是 通过管道通信

Pull: rsync [OPTION...] [USER@]HOST:SRC... [DEST]
Push: rsync [OPTION...] SRC... [USER@]HOST:DEST

主机与路径之间用一个冒号 : 分隔,路径格式为 user@host:path

通过网络套接字连接 rsync daemon

远程主机运行 rsync 服务,持续 监听 特定端口,等待客户端的连接。

Pull: rsync [OPTION...] [USER@]HOST::SRC... [DEST]
	  rsync [OPTION...] rsync://[USER@]HOST[:PORT]/SRC... [DEST]
Push: rsync [OPTION...] SRC... [USER@]HOST::DEST
	  rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST

主机与路径之间用两个冒号 :: 分隔,路径参数格式为 user@host::pathrsync://user@host/path

通过远程 shell 连接,服务端启动临时 rsync daemon

远程主机无需启动 rsync 服务,而是 临时派生rsync daemon,它是单用途的 一次性 daemon,仅用于 临时读取 daemon 的配置文件

当此次 rsync 同步完成,远程 shell 启动的 rsync daemon 进程也会 自动结束

语法同上,区别在于选项部分须 指定 --rsh-e

Pull: rsync [OPTION...] [USER@]HOST::SRC... [DEST]
	  rsync [OPTION...] rsync://[USER@]HOST[:PORT]/SRC... [DEST]
Push: rsync [OPTION...] SRC... [USER@]HOST::DEST
	  rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST

rsync 命令语法

基本语法

Local:  rsync [OPTION...] SRC... [DEST]

Access via remote shell:
  Pull: rsync [OPTION...] [USER@]HOST:SRC... [DEST]
  Push: rsync [OPTION...] SRC... [USER@]HOST:DEST

Access via rsync daemon:
  Pull: rsync [OPTION...] [USER@]HOST::SRC... [DEST]
        rsync [OPTION...] rsync://[USER@]HOST[:PORT]/SRC... [DEST]
  Push: rsync [OPTION...] SRC... [USER@]HOST::DEST
        rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST

常用选项

rsync 的选项实在太过丰富,此处按类别将常用的选项加以简要说明。

操作辅助

-v

显示同步过程中的 详细信息。使用 -vvvv 会获取 更多 的详细信息。

rsync -av --delete /media/hdd1/data1/ /media/hdd2/data2/

--dry-run

-n = --dry-run

模拟运行 将要进行的同步,以便了解将会发生的操作,确认是否符合用户初衷。常与 -vvvv 配合使用。

--progress

显示文件传输的 进度 信息。

连接选项

-e

指定要使用的 远程 shell,默认为 ssh。

--port

连接 rsync daemon 时使用的 端口号,默认为 873。

--password-file

连接到 rsync daemon 时使用,指定本地的某个 密码文件

rsync 会读取密码文件的内容,做为连接到服务端 rsync daemon 的密码。

  • 文件内容:只有一行,内容 仅仅为密码本身
  • 该文件权限: 他人无任何权限
  • 该选项仅供 rsync daemon 使用,对 ssh 无效
  • 通过远程 shell 连接到 rsync daemon 时,该选项仅在通过 ssh 认证之后开始生效

同步选项

-a

-a = --archive = -rtopgDl

归档模式:递归传输并保留属性,包括符号链接、权限、mtime、组、所有者等

--delete

使用该选项,服务端才会 同步删除

不用该选项,rsync 只会传输新建和修改过的文件,不理会发送端的删除。那些在发送端已删除的文件,在服务端仍会保留。

-r

-r = --recursive 递归 目录。

-t

-t = --times 保留 mtime 属性。

强烈建议任何时候都加上该参数,否则目标文件的 mtime 会设定为系统时间,导致下次会被更新。

-o

-o = --owner 保留 所有者 属性。

-g

-g = --group 保留 属组 属性。

-p

-p = --perms 保留 权限 属性,不包括特殊权限。

-H

保留 硬链接

-A

保留 ACL

-X

保留 扩展属性

-D

-D = --device --specials 的组合

复制 设备文件和特殊文件

-l

-l = --links 如果是 软链接,则复制软链接本身,而非所指对象。

-z

传输时进行 压缩,以提高效率。

-R

-R = --relative 使用 相对路径,而不仅使用最后一段。

意味着将命令行中指定的 全部路径参数 而非路径最尾部的文件名发送给服务端,包括它们的属性。

rsync -av dir1/host/tmp/rd.tmp remote:dest/ 中,源路径参数为 dir1/host/tmp/rd.tmp,rsync 默认仅会把 rd.tmp 发送给服务端。

rsync -av -R dir1/host/tmp/rd.tmp remote:dest/ 会把 dir1/host/tmp/rd.tmp 发送给服务端。

-d

-d = --dirs不递归 的方式复制目录本身。

默认递归时,如果源为 dir1/file1,不会复制 dir1 目录,使用该选项将复制 dir1 但不复制 file1

-W

-W = --whole-file 不再使用增量传输,而 使用全量传输。在网络带宽高于磁盘带宽时,该选项更高效。

--remove-source-files

删除 源端已经成功传输的文件。

限定传输文件

--max-size

设定 rsync 传输的 文件大小上限,更大的文件不会被传输。可以使用文件大小的单位后缀,如 m。允许使用小数。

--min-size

设定 rsync 传输的 文件大小下限,更小的文件不会被传输。

--exclude

指定 排除规则,来排除无需传输的文件。

--existing

只更新 目标端 已存在 的文件,目标端尚不存在的不传输。

注意,使用相对路径时,如果上层目录不存在,也不会传输。

--ignore-existing

只更新 目标端 不存在 的文件。

-m

-m = --prune-empty-dirs 从文件列表中 删除空目录

该参数告诉接收端把空目录从文件列表中去掉,包括嵌套的空目录。避免在筛选规则的作用下生成一堆没用的目录。

+ */ 与筛选规则配合使用时,经常会生成多个空目录,可以用该参数不传输这些空目录。

续传

--partial

-P = --partial

选项 --partial 告知接收端,如果在文件完整传输之前发送端不见了,请 保留未传输完成的文件

使用 rsync 传输文件时,文件被临时保存为目标目录中的一个隐藏文件,或保存在 --partial-dir 指定的临时目录中。

如果发生传输失败:

  • 不使用 --partial 选项:该 隐藏文件会保留在目标目录 中不动
  • 使用 --partial 选项:该 隐藏文件会被更名为真正的目标文件名,即使没有传输完成。

下一次可以使用 --apend--apend-verify 选项来再次运行 rsync,以期进行续传。

因此,--partial 需要与 --apend 配合使用,才能实现断点续传。

--append 选项

--append 选项是真正的续传开关,无论是否使用了 --partial 选项,都可以使用它。

当使用 --append 选项时,不会再创建临时文件,文件会被直接创建到目标,于是,突然的中断,产生的结果与使用 --partial 是一致的,留下的未传完的文件均是真正的文件名,只不过 --append 不会产生那些隐藏的临时文件。

如果要续传大型文件,在续传时,应使用 --append--append-verify 开关。

rsync 是在版本 3.0.0 之后,开始支持 --append-verify 选项的。

版本 3.0.0 之前:

  • --append 始终会比较两端的文件,不会假想它们相同

在传输之前会对目标文件进行完整的校验,如果检验失败就会从头重新传输。

版本 3.0.0 之后:

  • --append 会认为两端的文件是完全相同的,不会比较两端的文件,不做校验而直接传输
  • --append-verify 始终会比较两端的文件,进行校验,不会假想它们相同

文件的检查

检查文件是否有变化是确定是否要传输该文件的前提。

-u

-u = --update 忽略 那些接收端比发送端要 的文件。

注意:该选项是接收端判断的,不会影响删除行为。

--ignore-times

--ignore-times 选项会 关闭 rsync 对文件时间和大小的检查,无条件地传输所有文件

--size-only

只检查文件大小,相同就忽略。

-c

-c = --checksum

该选项会改变 rsync 检查文件的方法。

不用该选项时,在传输之前,rsync 默认使用快速检查,即检查每个文件的大小和修改时间。

使用该选项以后,rsync 会 忽略文件时间,只检查文件大小。如果文件大小相同,则进一步检查校验码

生成校验码 意味着,在传输之前,两端主机都要花费大量的时间在磁盘 I/O 上,读取文件中的所有数据。这会 严重影响传输速度

发送端扫描文件系统并生成文件列表时,会创建校验码。接收端在扫描变化的文件时生成校验码。

  • 如果发现 文件大小相同,则会 进一步检查其校验码 ,如果 检验码不同,则仅传输该文件的差异部分
  • 如果发现文件 大小不同,会 重新传输整个文件,覆盖目标文件。

因此,--checksum 选项最适合其文件内容经常有 小变化的大型文件的同步

要注意一点,rsync 传输的每个文件传到对端后,都要进行整个文件的校验,以 确保文件被正确重建。但该检验是在文件 传输之后,无论如何都要进行的,与 --checksum 的作用完全不同,后者是在 传输之前 想弄清楚 对端文件是否需要更新

--whole-file 选项

使用 -W--whole-file 选项时,rsync 不会使用 delta-transfer 机制,即会 传输整个文件,而非仅仅不同的部分。

当网络带宽大于本地磁盘的带宽时,使用该选项会加快同步速度。因此它是本地同步的默认选项。

服务端主机上的操作

😈 rsync 同步时,希望尽量 减少不必要的网络传输。因此,无论本地还是服务端,能本地解决的,就决不应该通过网络传输来解决。

某些时候,可能需要在目标主机上同时保存某个文件的多个副本,如果能让发送端知道目标主机已经存在该文件,就无需重复传输。

--compare-dest 选项

--compare-dest=DIR

DIR 为目标主机上的目录。当目标目录中某文件丢失,可能需要重传时,该选项会让 rsync 在传输之前,先在本地 DIR 寻找,如果找到了相同的文件,就不会传输该文件

可以使用多个 --compare-dest 选项来指定多个比较目录,rsync 会按顺序依次在这些目录中寻找。

如果在这些目录中找到了相同的文件,但文件属性不同,rsync 会生成一个本地副本,并将其更新为所需的属性。

如果没有找到,会找到一个接近的文件,以尝试加速传输。

如果 DIR 用的是相对路径,它一定要相对于目标目录。

--copy-dest 选项

--copy-dest=DIR

工作原理与上面选项类似。如果在目标主机的比较目录中 找到了相同的文件,rsync 就不会从网络传输,而是从该目录中 把文件从本地复制到目标目录

--link-dest=DIR

工作原理相同。如果在比较目录中找到的文件不仅与目标文件大小时间相同,而且 属性也完全一致,则会从比较目录把该文件 硬链接到目标目录,可以大大减少不必要的磁盘占用。

如果发现 rsync 没有生成链接,首先应该检查两地的文件属性是否一致,另外还要检查是否某些属性被管控,而导致 rsync 无法检查。比如挂载选项将超级用户作为了唯一合法用户,导致普通用户无权读取文件属性;或用了普通权限挂载可移动驱动器。

该选项特别适用于 向空目录复制文件。做了硬链接以后,rsync 就不用再去比较目录查找,而且其延展性很强,如果哪天需要修改所有文件的属性,只需要修改比较目录中的文件,其硬链接就自动跟着变化了。

--delete

同步时,以源为基准,对服务端的文件进行操作。多则删,少则补

--delete 是在接收端执行的,所以它是在 exclude/include 规则生效之后才执行的。

-b

-b = --backup 对目标上已存在的文件做一个 备份,备份文件后缀为 ~

--backup-dir

指定 备份文件的保存路径。不指定时默认与原文件同目录。

小结

  • 如果经常使用 rsync 来单纯地把文件从 A 移动到 B,只需要续传功能,不要使用 --checksum,一定要用 --append-verify
  • 如果使用 rsync 来备份文件,而且备份的主要为大型文件,其修改内容通常很少,可以使用 --append-verify

范例

rsync -azvHP -e ssh --delete --exclude Cache --link-dest=yesterdaystargetdir remote1:sourcedir todaystargetdir
  • 遍历子目录
  • 复制符号链接
  • 保留权限
  • 保留 mtime
  • 保留组
  • 保留所有者
  • 保留设备文件及特殊文件
  • 保留硬链接
  • 使用 SSH 做为服务端 shell
  • 同步删除动作
  • 排除 Cache 目录
  • 比较目录
  • 支持续传
  • 传输前压缩
  • 显示传输进度

rsync 路径参数注意事项

传输的根

rsync 所传输的文件结构往往是一个经筛选后得到的目录树结构,该目录树的最根一层目录,称为传输的根目录,或传输的根(root of the transfer)。与 Linux 根目录没有直接关系。

如:本地主机有目录 /tmp/dir1/test/,如果用 rsync 把 /tmp/dir1 目录及其内容同步给服务端主机,则 dir1 就是传输的根。

指定多个源路径

通过使用 多个 host:file 参数,可以 同时指定多个源文件路径,第二个起可以 省略主机

rsync -av host:file1 :file2 host:file{3,4} /dest/
rsync -av host::modname/file{1,2} host:modname/file3 /dest/
rsync -av host::modname/file1 ::modname/file{3,4}

较早版本的 rsync 是先用空格分隔各目录,再用单引号括起来:

rsync -av host:`dir1/file1 dir2/file2` /dest
rsync host::`modname/dir1/file1 modname/dir2/file2` /dest

以上用法在新版本也可以使用,但不如之前的方法便于使用。

如果传输的文件名含有空格,可以使用 --protect-args 参数,或者把空格转义:

rsync -av host:`file\ name\ with\ spaces` /dest

只有一个路径参数

如果在 rsync 命令中 仅有一个路径参数,将被视为源路径,其效果类似于 ls -l,会列出该主机上的 文件列表,而 不会复制文件

源路径结尾的斜线

当源路径是目录时,

  • 目录结尾有斜线 dir/,表示 同步目录中的文件,不包括目录本身
  • 目录结尾没有斜线 dir,表示 同步整个目录,包括目录本身
~]# rsync /etc /tmp
~]# rsync /etc/ /tmp

第一个命令会在 /tmp 目录下创建 etc 目录,而第二个命令不会,源路径 /etc/ 中的所有文件都直接放到 /tmp 目录下。

注意,无论目录结尾有否斜线,目录的属性始终会传输给对端

rsync -av /src/foo /dest
rsync -av /src/foo/ /dest/foo

🚩 即,以上两种情况的结果是一致的,会以同样的方式复制文件,而且 /dest/foo 的属性最终也是一致的。

服务端主机路径的省略

如果服务端主机或模块的末尾 省略了路径,则代表路径为 默认目录

通过 ssh 连接时,默认目录为 Linux 用户家目录:

rsync -av src/ host:

连接到 rsync daemon 时,默认目录为该模块指定的目录:

rsync -av host::module /dest

如果在服务端主机后 :: 省略了模块,将查看该主机 rsync 模块列表:

rsync somehost.mydomain.com::

隐含目录

Implied Directories

默认行为

默认情况下,rsync 只会传输命令行中源路径参数中最后一个斜线后面的字符串,可以是文件,也可以是目录。

rsync -av /foo/bar/baz.c remote:/tmp/

该命令会把本地的 baz.c 文件复制到服务端主机的 /tmp/ 目录,同步结果为在服务端创建 /tmp/baz.c。前面的 /foo/bar/ 被忽略。

rsync -av /foo/bar/baz remote:/tmp/

该命令会把本地的 baz 目录复制到服务端 /tmp/ 目录,即 /tmp/baz。前面的 /foo/bar/ 被忽略。

使用 -R 选项

而当使用 -R 选项时,rsync 会使用相对路径,即把命令行中完整的路径参数全部发送给接收端,而不仅仅是路径参数的最后部分。非常适用于 需要传输多个不同的目录 的场合。

rsync -avR /foo/bar/baz.c remote:/tmp/

使用 -R 后,baz.c连同其上层目录结构 一起被复制到服务端,同步结果为在服务端创建 /tmp/foo/bar/baz.c。这些额外的路径元素被称为 隐含目录

限制隐含目录的层级

如果 不希望 源路径参数 全部 做为隐含目录发送给对方,对于每个路径参数,都可以指定哪一部分要做为隐含目录。

通过在源路径参数中加入 ./ 来分隔路径参数,只有其后面的才会做为隐含目录发送给接收端。

rsync -avR /foo/./bar/baz.c remote:/tmp/

该命令的结果是在接收端创建 /tmp/bar/baz.c

rsync 筛选规则

写到这里,必须抱怨一句,rsync man page 写的实在是过于难懂了,逻辑也很混乱!导致普通用户的学习成本急剧增加,浪费了很多本不该浪费的时间。这就是本人倡导 “用简洁清晰的语言讨论技术” 的原因。能写代码、编程序是一回事,而能把事情讲清楚是另一回事。

通过筛选规则可以灵活 选择需要同步或排除的文件

--include 选项可以指定需要传输的文件,称 包含规则

--exclude 选项可以排除无需传输的文件,称 排除规则

rsync 会根据命令行中的 --include--exclude 选项 依次 生成一个过滤 规则列表

筛选的原则

  • 排除了目录,就排除了其中所有文件
  • 包含了目录,不会自动包含其中的文件
  • 命令行选项中,--filter 支持全部规则的解析,而 --include--exclude 只支持使用 ! 来清除规则列表。
  • 使用 --filter 选项时,在规则的 开头必须指定规则名称
  • --include--exclude--filter 一次只能指定一条规则,要想指定多条规则:
    • 可以在命令行上 多次使用 选项
    • 可以 从文件读取 规则:--filter=". mergefile",或 --include-from--exclude-from

生成要文件列表之后,对于其中的每个文件,rsync 都会对照筛选规则列表 依次 检查,并按 首个匹配的筛选规则 行事,该条规则:

  • 如果是排除,便忽略该文件不同步
  • 如果是包含,则不会忽略该文件
  • 如果没有匹配的规则,也不会忽略文件文件

根据 rsync 的以上工作特点,我们在指定筛选规则或编辑规则列表时,一定要 注意先后顺序。因为 rsync 会 按照匹配到的第一个规则行事

因此,编写规则通常会按以下顺序进行:

  • 设定包含规则
  • 逐层包含子目录
  • 设定排除规则

筛选规则的生效时机

发送端运行 rsync 命令后,rsync 会立即 扫描 命令行中给定的文件和目录,扫描过程中还会按照目录进行排序,将同一个目录的文件放在相邻的位置,称为 拷贝树(copy tree)。扫描完成后将需同步的文件或目录记录到 文件列表 中,然后将文件列表 传输给接收端

筛选规则的作用时刻是在扫描拷贝树 时,所以会根据规则来匹配,并决定是否把文件记录到文件列表中。

实际上,即使是排除的文件,也会记录到文件列表中,只不过被标记为 hide 而隐藏起来。

筛选规则的生效时间在 rsync 整个同步过程中是非常靠前的,它会影响很多选项的操作对象,最典型的如 --delete

从文件读取规则

使用 --filter=--include-from--exclude-from 选项都可以从文件读取规则。

  • 使用 --include-from--exclude-from 选项读取规则文件
  • 通过 --filter="merge file"--filter="dir-merge file" 可以把整个规则文件合并到筛选规则中来
rsync -av --include-from=include.file src/ dest/
rsync -av --exclude-from=include.file src/ dest/

规则文件有两种形式:单独 的规则文件,以及 每目录 一个独立的规则文件

单独的规则文件

--filter="merge file" 或简写为 --filter=". file"

单独的规则文件用于一次全部读取,其规则会被合并到规则列表中。

每目录的规则文件

--filter="dir-merge file" 或简写为 --filter=": file"

rsync 会遍历扫描每个目录,寻找目录中的特定名称(命令中指定的,如上 file)的规则文件,如果存在,则将其内容合并到当前继承的规则列表中。

这些每目录规则文件必须在发送端创建,因为要扫描发送端来决定传送哪些文件。

如果希望这些规则文件能够控制接收端避免删除某些文件,则需要将它们发送给接收端。

在每目录规则概念出现以后,有必要区分一下全局规则和每目录规则:

  • 全局规则:本次 rsync 同步过程中全程有效的规则,基本上在 dir-merge 之前读取的规则只要不重复就是全局规则
  • 每目录规则:仅对每个目录自身有效,加上其继承的父目录的规则
merge 使用的修饰符

在 merge 或 dir-merge 后面也可以跟修饰符,如 --filter=":n- file"

修饰符的主要作用是让 rsync 更好地了解该规则文件,以及应该如何使用它。

- 规则文件仅由排除表达式组成,不含其他规则。

+ 规则文件仅由包含表达式组成,不含其他规则。

C 规则文件应该以 CVS 兼容方式来读取。该修饰符同时会开启 nw-,同时也允许使用 ! 来反向指定,如果没有指定文件名,默认会使用 .cvsignore

e 传输时排除规则文件本身。即规则文件自身不会被传输。

n 规则不会被子目录继承。

w 规则使用空格分隔,而非换行符。此时不允许规则中使用注释。

除了以上这些修饰符,merge 还可以使用下文中的为 +- 准备的修饰符(除了 !,它用不上)。

如:

:s 表示每目录的规则文件仅在发送端生效。

如果命令行中的 merge 选项中已经使用了 sr 这类令规则仅在一端生效的修饰符,则其读取的规则文件中就不能重复出现它们。

子目录的规则

每目录规则默认会被其子目录继承,除非使用了 n 修饰符。

如果子目录中存在自己的规则文件,则其自己的规则会排在它继承规则的前面,具有更高的优先级。

防止规则被继承的方法

命令行中 merge 的位置,决定了全体每目录规则在全局规则列表中的位置。因此,如果在 命令行中更靠前的位置指定规则,会覆盖每目录的规则,因为它位于全局规则列表中更靠前的位置。

从每目录规则文件中读取到的 !,即清除列表规则(list-clearing rule),只会清除其继承的规则。

另外,如果在规则中使用前导斜线 / ,就可以阻止该条规则被继承。因为每目录规则文件中的规则,均是相对于规则文件所在的目录的,因此规则中的 /foo 只会匹配该规则文件所在目录中的 foo,而不会匹配其子目录中的 foo

使用每目录规则时,删除的注意事项

如果没有删除的选项,每目录规则只与发送端有关,因此完全可以使用 --filter=":e .file" 把规则文件自身给排除掉,不予传输。

但是,如果需要在接收端进行删除操作,而且还需要排除某些文件,以避免被删除,则 必须要保证把所有规则文件都发送给接收端,这样两端的主机所获得的规则才完全相同。之后,需要 使用 --delete-after 选项,来告知接收端,直到 接收完所有的规则文件才开始执行删除的操作

rsync -avF --delete-after host:src/dir /dest

筛选规则的语法

规则名称[,修饰符] [表达式或文件名]

规则名称有两种形式:长名,如 exclude。短名,如 -。因短名只有一个字符,因此更多被使用。

  • 使用 长名exclude,/ dir1/mergefile
  • 使用 短名-,! */
  • 使用 短名 时,可以省略修饰符前面的逗号:-p *.tmp

规则名称

本段落中规则名称均为 长名, 短名 格式。

exclude, - 指定要排除的文件。只作用于发送端,被排除的文件在文件列表中与隐藏规则一样被隐藏

include, + 指定要包括的文件。只作用于发送端,被包含的文件将明确记录到文件列表中

merge, . 指定额外文件,从中读取更多规则

dir-merge, : 指定额外目录,读取其中的所有规则文件

hide, H 指定传输中要隐藏的文件。只作用于发送端,隐藏后的文件对于接收端来说是看不见的,也就是说接收端会认为它不存在于源端。

show, S 指定要显示的文件。只作用于发送端,是隐藏规则的反向规则。

protect, P 指定要保护的文件。只作用于接收端,被保护的文件不会被删除掉。

risk, R 指定不保护的文件,可以删除

clear, ! 清除当前规则列表

如果从文件中读取规则,规则文件中的空行将被忽略,# 开头的行视为注释。

修饰符

筛选规则中的修饰符是为了 更加细致地规范规则

以下的修饰符可以在 +- 后面使用:

/

/ 表示规则匹配时必须 按绝对路径比较

-/ /etc/passwd 表示:从 /etc 目录发送的文件 passwd 会被排除

-/ subdir/foo 只要文件 foo 所在的目录为 subdir 就会排除,哪怕 foo 位于传输的根上。

!

! 表示反向比较,可以理解为 “”。

-! */ 表示排除所有非目录。

s

s 表示规则仅在发送端生效。

如果规则在发送端生效,该规则就会 防止文件被传输

普通的规则默认是在发送端和接收端同时生效的。

r

r 表示规则仅在接收端生效。

如果规则在接收端生效,该规则就会 防止文件被删除

C

本人未了解。

p

当需要删除某个被排除的文件所在的目录时,可以使用 p 修饰符。

假设本地主机有如下目录结构:

data
├── cache
│   └── config.tmp
├── dir1
│   └── go.tmp
├── dir2
│   └── over.tmp
└── logs
    └── error.log

同步时,我们不希望传输本地的 *.tmp 临时文件,以及 data/logs 目录中的日志文件,因为它们只对本地系统有意义。同时,我们也希望保存服务端主机上的临时文件和日志文件,不要被 rsync 删除。

假设之前已经同步过,现在删除本地 data/cache 目录,然后再次同步:

$ rm -rf data/cache
$ rsync --delete -d --exclude="*.tmp" --exclude="data/logs/*.log" src/ remote:dest/

运行时会得到 “cannot delete non-empty directory: data/cache” 的反馈信息,因为 data/cache/config.tmp 是被排除的文件,在服务端主机上没有被删除,rsync 就无法删除其所在目录。

此时,即使加上 --force 这个强制删除非空目录的选项也无济于事。

--delete-excluded 选项,虽然可以删除被排除的文件,但并不适合当前场景,因为我们需要保留服务端的 *.tmp*.log 文件。

rsync 有一个模式叫 “易逝” 模式(perishable mode),用 p 修饰符来实现。

该模式下排除的文件不会传输,通常也不会在接收端被删除,除非 它们所在的目录不复存在。也就是说,如果 rsync 要删除某个目录时,发现其中有被排除的文件,会临时忽略排除的限制,继续删除该目录。

因此,最终我们应该使用修饰符 p 来实现:

$ rsync --delete -d --filter="-p *.tmp" --filter="-p data/logs/" src/ remote:dest/

该命令运行后,接收端 data/cache 目录将被成功删除。

表达式

表达式可以有以下几种形式:

/ 前导斜线

如果表达式以 / 开头,则该表达式需要锚定到某个目录上。

  • 对于命令行中指定的规则与单独规则文件中的规则来说,该前导斜线表示 相对于传输根
  • 对于每根目录规则文件来说,该斜线表示 相对于规则文件所在目录
结尾斜线 /

如果表达式以斜线结尾,表示 仅匹配目录,而非普通文件、链接或设备文件。

通配符

只有在表达式中发现 *?[] 时,rsync 才会进行通配符匹配,否则进行简单的字符串匹配。

*

* 匹配所有文件、路径,但遇到斜线 / 会停止匹配。

**

** 匹配所有文件、路径,包括斜线。

?

? 匹配所有字符,除了 /

[ ]

[ ] 会引入一组字符,如 [a-z][[:alpha:]]

\

在通配符表达式中,反斜线 \ 可用于转义通配符本身。

如果在表达式中不含通配符,\ 匹配反斜线自身。

/**

如果表达式包含一个 斜线 / (结尾斜线不算)或 双星号 **,则应该用于 匹配全路径

如果表达式 不包含 /\*\*,则仅 匹配文件名

dir/***

如果表达式的结尾是 dirname/***,则匹配 目录及其所有内容

表达式范例

- *.o 排除所有 *.o 文件

- /foo 排除传输根目录中名为 foo 的文件或目录

- foo/ 排除所有名为 foo 的目录

- /foo/*/bar 在传输根目录中,排除 foo 目录下两层子目录中名为 bar 的文件

- /foo/**/bar 在传输根目录中,排除 foo 目录下两层及两层以上子目录中名为 bar 的文件

+ */, + *.c, - * 包含所有目录,以及所有 *.c 文件,排除其他所有

+ foo/, + foo/bar.c, -* 包含名为 foo 的目录,及该目录中的 bar.c 文件,排除其他所有

包含文件或目录

要想准确地包含文件或路径,首先要 逐层目录地包含最后再把其余的排除掉

简单粗暴型:

+ */

细腻型:

+ data/
+ data/text_forms/
+ data/text_forms/*.per
+ data/text_forms/*.42f
+ data/text_forms/*.xml
+ data/text_forms/*.xml.*
- *

只把要包含的目录单独指定,然后包含目录中特定文件,最后再排除其他所有。

排除其余所有:

- *

连接到 rsync daemon

可以不使用远程 shell 来传输,而直接连接到远程 rsync daemon,通常使用 TCP 873 端口

rsync daemon 会为每个连接 fork 出一个新的 rsync daemon 进程。进程启动时,会解析 rsyncd.conf 配置文件,了解存在哪些模块、设置全局选项。

当 rsync daemon 收到针对某个 rsync 模块的连接时,它会 fork 出一个子进程来处理该连接。该子进程读取 rsyncd.conf 为该模块请求设定选项,该模块会 chroot 到其默认路径,并为该进程去掉 SUID、SGID。

之后,该子进程就可以像其它 rsync 服务端进程一样运行了。

基本语法

Access via rsync daemon:
  Pull: rsync [OPTION...] [USER@]HOST::SRC... [DEST]
        rsync [OPTION...] rsync://[USER@]HOST[:PORT]/SRC... [DEST]
  Push: rsync [OPTION...] SRC... [USER@]HOST::DEST
        rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST
rsync -av host::src /dest

【 daemon 选项 】

--daemon 作为 daemon 运行

--address 绑定到特定 IP 地址或主机名,此举允许虚拟主机配合 --config 选项

--config=FILE 不使用默认的配置文件,指定本次运行 rsync 要使用的配置文件

--port=PORT 指定运行端口

与使用远程 ssh 的区别

  • 主机后要用双冒号 ::,或使用 rsync:// 格式
  • 主机后的第一个名字是模块名 host::module
  • 服务端 rsync daemon 可以输出一条消息告知你连接的日期
  • 如果省略服务端路径,则会列出服务端可访问的路径列表
  • 如果没有指定本地目标目录,则会列出服务端目录中文件列表
  • 不可使用 -rsh (-e) 参数

关于模块的概念见下文 “rsync daemon 的配置”

用户认证

根据实际需要,可以对服务端 rsync daemon 中的某些模块进行强制密码认证,本地连接到服务端时,会收到输入密码的提示。要想避开该提示,可以使 --password-file 选项来指定 本地保存密码的文件,也可以使用环境变量 RSYNC_PASSWORD 来保存密码。

rsync -av --password-file=pwd source/ host::module

通过代理连接

WEB 代理

可以 通过 WEB 代理服务器来建立连接,只需给环境变量 RSYNC_PROXY 赋值,格式为 hostname:port。注意,网页代理服务器的配置必须支持到 TCP 873 端口的连接。

程序代理

也可以通过设定环境变量 RSYNC_CONNECT_PROG 为某个命令,实现 把一个程序做为代理来创建一个 rsync daemon 的连接,这样就可以不用直接创建一个套接字连接了。

export RSYNC_CONNECT_PROG='ssh proxyhost nc %H 873'
rsync -av targethost1::module/src/ /dest/
rsync -av rsync:://targethost2/module/src/ /dest/

以上命令表示使用 ssh 在代理主机 proxyhost 上运行 nc (netcat),netcat 会把所有数据转发给主机 %H 的端口 873。

%H 代表 rsync 命令中的主机名,hostname。

rsync daemon 的配置

启动 rsync daemon

要想连接到服务端系统的 rsync daemon,该守护进程必须在服务端系统中启动,或者当 特定端口传来入站连接时,设置诸如 inetd 等来 自动生成一个 rsync daemon

/etc/rsyncd.conf 是 rsync daemon 的配置文件。

如果使用了远程 shell 来传输,就不必再手动启动 rsync daemon 了。

通过远程 shell 使用 rsync daemon 的功能

如果不用放行任何新的套接字连接进入系统,而又能使用 rsync daemon 的各种功能,有时候会特别有用。

rsync 允许使用远程 shell 连接到主机,然后再产生一个单一用途的 rsync daemon,该进程可以按指定选项来读取配置文件。

当你需要加密 daemon 风格的数据传输时,这个方法特别有用。但是,因为该进程是由服务端用户激发的,因此无法使用 chroot 功能,也无法修改进程使用的 UID。

另一种加密 daemon 传输的方法是:用 ssh 为某个本地端口到服务端主机先创建一个隧道,然后再在服务端主机上配置一个普通的 rsync daemon,配置其只接受本地主机的连接。

语法上基本与使用 ssh 相同,只是要用 --rsh 来指定远程 shell。当然也可以通过设置环境变量 RSYNC_RSH 达到相同的效果。

rsync -av --rsh=ssh host::module /dest

如果要使用其它的远程 shell 用户,可以使用 user@host 格式来指定,同时必须使用 -l user 选项:

rsync -av -e "ssh -l ssh-user" rsync-user@host::module /dest

注意:ssh-user 用于 SSH 连接,而 rsync-user 则用于登陆 rsync 的模块。

批处理模式

批处理模式可用于 向多组相同的系统应用同一批更新

假设有个目录树,在 多个主机上都有副本,该源目录树发生的修改需要传给多个主机。

为了使用批处理模式,先是使用 --write-batch 选项,向某一台主机的目标来应用修改,--write-batch 选项会把本次 rsync 对服务端进行的同步操作 完整保存到一个 批处理文件 中,之后可以用它来帮助其余的主机完成快速同步。

为了方便用户,rsync 同时还会自动生成一个 同名脚本文件 *.sh,内容为一行 rsync 命令,上面的批处理文件也会做为其中的选项。

批处理文件的作用

之所以在第一次同步时生成批处理文件,是为了省去同步其余主机时不必要的文件状态检查、校验码检查,可以大大提升同步的效率。可以用 多播传输协议同时对多个主机并行传输更新的文件,而无需单独对每个主机逐个传输相同的数据。

如何使用批处理文件

要想把批处理录制下来的同步操作应用给其他主机,可以在同步命令中使用 --read-batch 选项来指定批处理文件。

生成批处理文件

假设有 A、B、C、D、E、F、G 七台主机,同时保存相同的目录树副本,现在主机 A 上对目录树做了修改,希望其同步到其余所有主机上。

可以先随意找一台主机,如主机 B,先在上面运行一次 rsync,把主机 A 中的修改同步到主机 B 的 /destB/dir/ 目录。

rsync --write-batch=abc -av hostA:/source/dir/ /destB/dir/
#                   ^^^

同步完成后,rsync 会生成两个文件:abcabc.sh

  • abc 为批处理文件,用于记录本次 rsync 的所有同步操作。如对文件的删除、同步等。
  • abc.sh 为脚本文件,其内容为 rsync -av --read-batch=abc ${1:-/destA/dir/}

abc.sh 可以用 bash shell 来执行,运行时还可以传递一个 目标目录 的参数,该参数将覆盖脚本中的目标目录。当目标主机中的目标目录名各不相同时非常有用。

把批处理文件复制到其他主机

scp abc* hostC:

使用 scp 命令把 abcabc.sh 复制到主机 C。

远程执行同步脚本

ssh hostC ./abc.sh /destC/dir/

用 ssh 连接到主机 C,并在主机 C 的 shell 上执行刚刚复制过去的脚本 abc.sh,即在主机 C 上运行 rsync 进行同步。

从标准输入读取批处理

ssh hostD rsync --read-batch=- -a /destD/dir/ <abc
#                            ^                 ^^^

也可以在 ssh 命令中直接使用 rsync 命令,此时 --read-batch 选项的值来自于重定向于 abc 的标准输入。

这样的做法就省去了向服务端主机复制批处理文件的步骤。

本例未使用脚本 abc.sh 是因为 --read-batch 选项需要使用标准输入。如果想在命令中使用脚本,必须要确保没有其他选项会使用标准输入,如 --exclude-from=-

批处理注意事项

使用 --read-batch 选项时,必须确保目标目录树与生成批处理文件时的目标目录树完全一致。

  • 如果目标目录树已经更新,同步时会被忽略,并出现警示信息
  • 如果目标目录树出现验证错误,同步会忽略,并出现错误信息

因此,如果同步被意外中断时,可以很安全地使用 --read-batch 再次运行 rsync 命令。

在批处理模式中,如果希望每次同步时都忽略对文件大小和日期的检查,可以再加上 -I 选项。

如果同步时的意外导致目标没有同步完成,可以使用 rsync 的普通模式来修复。

同步的目标主机,其 rsync 版本不能低于生成批处理的主机。

生成批处理文件时,如果在 rsync 命令中含有 --exclude--include 或其他过滤选项,这些选项会被保存到 *.sh 脚本中,并以 here 文档的格式追加在 rsync 命令的下面:

~]$ cat abc.sh
rsync --filter=._- -av --read-batch=abc ${1:-~/source/} <<'#E#'
- aaaa
- bbbb
#E#

上例中 - aaaa- bbbb 就是被排除的目录名。

rsync 工作流程分析

基本概念

工作方式

rsync 有三种工作方式:

  • 本地传输
  • 使用远程 shell 连接
  • 使用网络套接字连接 rsync daemon
本地传输

本地传输其实是一种特殊的工作方式。运行 rsync 命令时,

  • 先生成一个 rsync 进程
  • 根据此进程 fork 另一个 rsync 进程 作为连接的对端

连接建立之后,后续所有的 通信 将采用 管道 的方式。

使用远程 shell 连接

以 SSH 为例:

  • 本地键入 rsync 命令后,将 请求 和远程主机建立远程 ssh 连接,连接建立成功后
  • 在远程主机上 fork 远程 ssh 进程
  • 调用远程 rsync 程序
  • 将 rsync 所需的 选项 通过远程 ssh 传递给远程 rsync

这样两端就都启动了 rsync。之后,它们将通过 管道 的方式(即使它们之间是本地和远程的关系)进行通信

使用网络套接字连接 rsync daemon

通过网络套接字连接到 rsync daemon 时,

  • rsync daemon 进程会 创建一个子进程 来响应该连接,并负责后续该连接的所有通信

这样两端也都启动了连接所需的rsync,之后它们 通过网络套接字进行通信

客户端与服务端

无论使用何种连接方式,发起连接的一端被称为 客户端,即执行 rsync 命令的一端,连接的另一端称为 服务端

服务端可以是本地端,也可以是远程对端,还可以是远程 rsync daemon 端。

rsync 的客户端和服务端的概念存活周期很短,当客户端和服务端都启动了 rsync 进程,并建立了 rsync 连接(管道、网络套接字)时,将使用 发送端接收端 来代替客户端和服务端的概念。(啰嗦)

进程及流水线

两端的 rsync 连接建立后,整个传输过程将由三个进程完成,它们是 高度流水线化(pipelined)的。进程之间以 单方向 的方式进行通信。在文件列表传输完成之后,流水线如下:

generator > 发送端 > 接收端

generator 的输出结果是发送端的输入,发送端的输出结果是接收端的输入。

它们每个进程独立运行,并且只有在流水线被阻塞,或等待磁盘 I/O、CPU 资源时才被延迟。

虽然这三个进程是流水线式的,但它们是 完全独立、并行工作 的。每个进程在处理完相关工作的那一刻,都会立即将数据传输给接收进程,并开始处理下一个工作,接收进程收到数据后也马上开始处理。所以它们的工作基本上不会出现延迟和阻塞。

发送端进程

发送端 rsync 进程负责这一端的 所有工作

发送端进程收到 generator 的 校验码集合,会立即处理该文件。处理文件时,每遇到一个匹配块,都会 随时 将这部分相关数据 传递 给接收端进程,然后立即处理下一个数据块。

接收端进程

接收端 rsync 进程负责 接收数据、文件重组

接收端进程收到数据后,会立即开始 重组 工作。

接收端 generator 进程

它是另一个核心进程,负责执行 删除 操作、比较文件大小比较 mtime、对每个文件 划分数据块计算校验码生成校验码集合,然后将校验码集合传递给发送端。

generator 计算出一个文件的校验码集合,传给发送端,之后它会立即计算下一个文件的校验码集合。

文件列表

rsync 连接建立完成之后,第一件事就是由发送端创建文件列表,并传递给接收端。

文件列表中不仅包含路径,还包含文件的 部分属性:复制模式、权限、文件大小、所有者、属组、修改时间等。

包含哪些属性决定于 rsync 使用的选项,如不指定 -o-g 时,不会包含 UID、GID,指定 --checksum 会包含文件级的校验码。

文件列表创建并传递完毕以后,两端都会按照相对于传递根目录的路径对文件列表进行排序(排序算法信赖于传输的协议版本号),排序完成后,之后对文件的引用就都通过文件列表中的 编号 来进行。

以下为实例:

~]# rsync -a -vvvv /etc/cron.d /var/log/anaconda /etc/issue longshuai@172.16.10.5:/tmp
......
######### 发送端生成文件列表,发送给接收端 #############
sending incremental file list
[sender] make_file(cron.d,*,0)       # 第一个要传输的文件目录:cron.d文件,注意,此处cron.d是待传输的文件,而不认为是目录
[sender] make_file(anaconda,*,0)     # 第二个要传输的为目录:anaconda
[sender] make_file(issue,*,0)        # 第三个要传输的为目录:issue

# 从文件列表的第1项开始,本次要传输的文件共有3个
[sender] flist start=1, used=3, low=0, high=2   
# 为这3个文件生成列表,包括文件id,所在目录,权限模式,长度,uid/gid,修饰符
[sender] i=1 /etc issue mode=0100644 len=23 uid=0 gid=0 flags=5   
#  隐含目录  ^^^^  ^^^^  需同步文件
[sender] i=2 /var/log anaconda/ mode=040755 len=4096 uid=0 gid=0 flas=5
#  隐含目录   ^^^^^^^  ^^^^^^^  需同步文件
[sender] i=3 /etc cron.d/ mode=040755 len=51 uid=0 gid=0 flags=5   
#  隐含目录  ^^^^  ^^^^^^  需同步文件
send_file_list done
file list sent

唯一需要注意的是文件所在目录,例如 /var/log anaconda/,但实际在命令行中指定的是 /var/log/anaconda。此处 loganaconda 使用空格分开了,这个 空格 非常关键。空格的左边为 隐含目录,右边是 待同步的文件默认 情况下将会在接收端生成 anaconda/ 目录,而左边的 隐含目录不会创建

可以通过使用选项 -R,允许接收端同步时 创建隐含目录,以便创建 整个目录结构

############ 发送端发送文件属性信息 #####################
# 由于前面的文件列表中有两个条目是目录,因此还要为目录中的每个文件生成属性信息并发送给接收端
send_files starting
[sender] make_file(anaconda/anaconda.log,*,2)
[sender] make_file(anaconda/syslog,*,2)
[sender] make_file(anaconda/program.log,*,2)
[sender] make_file(anaconda/packaging.log,*,2)
[sender] make_file(anaconda/storage.log,*,2)
[sender] make_file(anaconda/ifcfg.log,*,2)
[sender] make_file(anaconda/ks-script-1uLekR.log,*,2)
[sender] make_file(anaconda/ks-script-iGpl4q.log,*,2)
[sender] make_file(anaconda/journal.log,*,2)
[sender] flist start=5, used=9, low=0, high=8
[sender] i=5 /var/log anaconda/anaconda.log mode=0100600 len=6668 uid=0 gid=0 flags=0
[sender] i=6 /var/log anaconda/ifcfg.log mode=0100600 len=3826 uid=0 gid=0 flags=0
[sender] i=7 /var/log anaconda/journal.log mode=0100600 len=1102699 uid=0 gid=0 flags=0
[sender] i=8 /var/log anaconda/ks-script-1uLekR.log mode=0100600 len=0 uid=0 gid=0 flags=0
[sender] i=9 /var/log anaconda/ks-script-iGpl4q.log mode=0100600 len=0 uid=0 gid=0 flags=0
[sender] i=10 /var/log anaconda/packaging.log mode=0100600 len=160420 uid=0 gid=0 flags=0
[sender] i=11 /var/log anaconda/program.log mode=0100600 len=27906 uid=0 gid=0 flags=0
[sender] i=12 /var/log anaconda/storage.log mode=0100600 len=78001 uid=0 gid=0 flags=0
[sender] i=13 /var/log anaconda/syslog mode=0100600 len=197961 uid=0 gid=0 flags=0
[sender] make_file(cron.d/0hourly,*,2)
[sender] make_file(cron.d/sysstat,*,2)
[sender] make_file(cron.d/raid-check,*,2)
[sender] flist start=15, used=3, low=0, high=2
[sender] i=15 /etc cron.d/0hourly mode=0100644 len=128 uid=0 gid=0 flags=0
[sender] i=16 /etc cron.d/raid-check mode=0100644 len=108 uid=0 gid=0 flags=0
[sender] i=17 /etc cron.d/sysstat mode=0100600 len=235 uid=0 gid=0 flags=0
# 从上述结果中发现,没有i=4和i=14的文件信息,因为它们是目录anaconda和cron.d的文件信息
# 还发现没有发送/etc/issue文件的信息,因为issue自身是普通文件而非目录,因此在发送目录前就发送了
############# 文件列表所有内容发送完毕 ####################

基准文件

Basis File

如果文件不被忽略,所有目标路径下 已存在的文件版本 将作为基准文件,它们将作为数据匹配源,使得发送端无需发送能匹配上这些数据源的部分,从而实现增量传输。

rsync 工作流程

建立连接,启动进程

客户端启动,与服务端 建立通信连接

在通信最初阶段,双方会相互发送自己支持的最大的 协议版本号,使用两者之中较低版本进行传输。

连接远程 shell

当 rsync 通过远程 shell 连接到服务端时,服务端会 启动一个 rsync 进程

两端都通过 远程 shell 之间的管道 进行通信。

在此过程中,rsync 进程未涉及到网络。

该模式下,服务端的 rsync 进程所使用的 选项 是由远程 shell 传递给它的。

连接 rsync daemon

连接到服务端的 rsync daemon 时,直接使用 网络套接字 通信。

这是唯一一种可称为网络感知的 rsync 通信方式。

该模式下,rsync 通过套接字先把 选项 传递给服务端,再传递排除列表。

本地

rsync 在本地同步时,源文件端变成发送端,并 fork 一个服务端进程,来扮演接收端的角色,之后它们通过 管道 进行通信。

发送端传递文件列表

发送端进程根据 rsync 命令行中给定的选项和筛选规则来 收集待同步文件,将其 保存至文件列表传递 给接收端。

  • 创建文件列表时,先按照目录进行 排序,然后给文件 编号,便于随后对文件的引用。
  • 无需等待全部文件扫描完毕,发送端每扫描完 一个目录,就会将这一部分的文件列表 随时 发给接收端。同样,接收端也是逐个目录接收的,收到的文件列表均经过排序。
  • excludehide 筛选规则匹配的文件会在文件列表中 标记为隐藏 hide,接收端看不见这些文件。

接收端比较文件,传递校验码集合

接收端收到文件列表后,从接收端进程 fork 出 generator 进程,该进程参照文件列表来 扫描本地目录树

删除操作

generator 的工作流程中,最先执行的是 删除 操作。

如果使用了 --delete 选项,会删除那些源路径中没有,但目标路径中存在的文件。

比较文件

参照文件列表,按顺序逐个 比较本地文件 的大小和 mtime。

目标文件存在

如果目标路径中的目标文件已经存在,则对两端文件的文件大小或 mtime 进行比较:

如果 相同则忽略,如果 不同,则对目标文件进行 校验

generator 读取文件列表中的一个基准文件,将其 划分数据块并编号,对每个数据块计算 弱滚动校验码(rolling checksum)和 强校验码(MD5 hash),将这些校验码随数据块编号组合在一起,成为 校验码集合

数据块的大及块校验和的大小,是根据文件大小计算出来的,也支持自定义块大小。将该文件的 编号校验码集合 一起传递给发送端。

每文件的块校验码集合在计算出来之后也是 随时 发送的,不会等所有文件都计算完才一起发送。

之后,继续处理文件列表中下一个文件。

如果使用了 --checksum 选项,generator 会生成 文件级校验码,并加以比较。

如果使用了 --whole-file 选项,则会对文件列表中的所有文件发送 空的块校验码,使得 rsync 强制采用 全量传输

目标文件不存在

当 generator 比较文件时,对于那些接收端 没有的文件,会将其 校验码设置为空,再传递给发送端。rsync 对于该文件就会使用 全量传输,而不再用增量传输。

发送端传递匹配信息、非匹配数据

发送端进程收到 generator 发来的 “文件编号及校验码集合”,对于 generator 发来的每个文件,发送端会存储块校验码,并生成它们的 hash 索引,以加快查找速度。

然后,按照文件编号,发送端依次 为本地文件数据块计算校验码。为了节省时间,先是仅计算滚动校验码。

发送端读取每个文件,在 所有偏移量上 查找,看有没有数据块与接收端相同。

读取相同大小的数据块,并计算校验码,将其与接收端的校验码集合进行 比对,看能否 匹配 其中的某一项。

没有匹配项

如果没有匹配项,表示两端数据存在差异,该数据块需要传输给接收端。

这部分不匹配的数据 累加 到上一个不匹配数据中。

向后 跳一个字节,比较下一个数据块。这就是所谓 “滚动检验”。

发现匹配项

如果发现匹配项,会再次计算该数据块的强校验码,若仍然相同,表示该数据块两端都有,无需传输。

此时,会把累加到当前的 不匹配数据 发送给接收端,然后发送 匹配项的偏移量及长度

向后 跳一个字节,比较下一个数据块。

即使文件的数据块被重新排序过,或位于不同的偏移量上,用这种方法都能 把匹配项找出来,这是 rsync 机制的 核心

通过这种方法,发送端会传递出丰富的指令,以帮助接收端重组出最新的文件。这些指令详细地描述了如何从基准文件中把匹配的数据复制出来,同时还包含所有的不匹配的原始数据(raw data)。

处理完每个文件,发送端还会把整个文件的校验码发给接收端,然后再处理下一个文件。

接收端重组文件

接收端会根据文件编号来读取发送端传来的该文件的数据,打开本地的基准文件,创建一个临时文件。

接收端收到的都是若干个 不匹配数据 + 匹配记录 这样的序列,用它们就能重组成一个最新的文件。

读取到的不匹配数据会被写入临时文件,接收端参照匹配记录的说明,定位基准文件的偏移量,将对应的部分复制到临时文件中。临时文件就是以这种方式一点点创建完成的。

临时文件生成完毕,会为其生成文件校验码,将其与发送端的进行比对,要是不同,则删除该临时文件。如果失败一次,会重新再生成一遍,如果失败再次,则会报告错误信息。

为临时文件设置拥有者、权限、mtime,然后重命名,覆盖基准文件。

从基准文件把数据复制到临时文件的过程,会让接收端的磁盘操作变得密集。对于小文件,可以放到磁盘缓存,但若是大文件,generator 要处理下一个文件时,缓存就会发生抖动(thrash),会导致发送端产生延迟。

另外,由于数据可能会从一个文件随机地被读取,再写到另一个文件中,如果整块数据大于磁盘缓存,则会导致 “寻道风暴” (seek storm),会进一步影响性能。

rsync 适用场景

资源消耗

增量传输时:

  • 发送端 因为要多次计算、多次比较各种校验码,因此对 CPU 的消耗很高。
  • 接收端 因为要从 basis file 中复制数据,因此对 I/O 的消耗很高。

全量传输时:

  • 发送端无需计算、比较校验码,接收端无需复制 basis file,rsync 消耗的资源与 scp 是一样的。

不适合同步数据库文件

数据库文件比较大,频繁访问。如果要用 rsync 同步,发送端在计算、比较数据块校验码时会长期消耗大量的 CPU,从而影响数据库服务的性能。

接收端每次都要从巨大的 basis file中复制大部分相同的数据块,来重组新文件,这几乎相当于直接 cp 了一个文件,I/O 压力特别大。

因此,对于数据库文件,只适合用 rsync 偶尔备份一次,不适合做频繁的同步。

数据库文件的同步应该 使用数据库自带的 replication 功能

适合实时同步大量小文件

rsync 最适合实时同步大量的小文件。

由于 rsync 是增量同步,所以对于接收端已存在的文件,不会发送。因此两端都只需要处理少量的文件。

由于文件较小,因此无论是发送端的 CPU 还是接收端的 I/O 都不成问题。

但,实时 同步需要 借助其它工具 来实现,如 inotify + rsync,sersync。