Linux 基础体系 · 第 50/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

rsync 文件同步:增量算法、权限、删除、断点和备份策略

rsync 是一种面向文件和目录树的同步工具。它可以在本机目录之间同步,也可以通过 SSH 在两台主机之间同步。它最重要的特征不是“复制文件”,而是:

  1. 先比较源端和目标端的文件状态;
  2. 对已存在但部分内容变化的文件,尽量只传输变化的数据块;
  3. 按选项决定是否同步权限、所有者、时间戳、符号链接等元数据;
  4. 可选地删除目标端多余文件;
  5. 可选地保留被替换或删除的目标文件;
  6. 中断后再次运行时,重新比较并继续传输尚未完成的内容。

rsync 不是事务型复制工具,也不是数据库备份系统。它通常不提供整个目录树在同一时刻的一致快照,--delete 还可能造成真实的数据删除。因此,理解其数据流、状态变化和失败路径,比记住一条“万能命令”更重要。


一、先建立模型:源、目标、文件列表和同步方向

一次同步有两个角色:

  • 源端(source):提供期望状态;
  • 目标端(destination):被修改以接近源端。

例如:

rsync -a /srv/data/ /backup/data/

这里:

  • /srv/data/ 是源端;
  • /backup/data/ 是目标端;
  • 目标端最终通常应包含源端目录树中的文件和元数据;
  • 目标端原本多出来的文件不会自动删除,除非使用 --delete

目录末尾的斜杠具有重要含义:

rsync -a /srv/data/ /backup/

复制的是 /srv/data/ 目录中的内容,结果通常是:

/backup/file1
/backup/subdir/file2

而:

rsync -a /srv/data /backup/

复制的是 data 这个目录本身,结果通常是:

/backup/data/file1
/backup/data/subdir/file2

可以将其概括为:

  • 源目录/:同步目录内容;
  • 源目录:同步目录对象本身。

在生产环境中,目录末尾少写一个 /,可能导致备份出现在错误层级;在 --delete 场景下,还可能让操作者误判删除范围。


二、rsync 的增量算法究竟做了什么

2.1 “增量”不等于只比较文件名

对一个目标端已经存在的普通文件,rsync 通常不会简单地执行:

删除目标文件
重新发送整个源文件

而是尝试回答:

源文件中哪些内容可以由目标端已有的数据复用?哪些内容必须从源端传输?

设源文件为 SS,目标端旧文件为 TT。目标端会将 TT 划分为若干块:

T=B1B2BnT = B_1 \Vert B_2 \Vert \cdots \Vert B_n

其中:

  • BiB_i 是第 ii 个数据块;
  • \Vert 表示拼接;
  • 块大小由实现和文件大小等因素决定,不应把某个默认块大小写死在脚本逻辑中。

目标端为每个块计算摘要,并将这些摘要发送给源端。源端扫描自己的文件 SS,寻找能够复用的目标块;对于找不到匹配块的部分,则发送“字面数据”。

最终,源端向目标端发送的是一种描述:

复用目标端的 B3
发送字面数据 "new..."
复用目标端的 B4
发送字面数据 "..."

目标端根据这些指令重新构造新文件。


2.2 滚动校验和为什么有用

如果源文件在开头插入了一行,后面所有内容的绝对偏移都会变化:

旧文件:
AAAA
BBBB
CCCC

新文件:
XXXX
AAAA
BBBB
CCCC

如果只按固定偏移比较,AAAABBBBCCCC 看起来都错位了,可能需要重新传输整个文件。

增量算法使用滚动校验和,使源端可以高效地尝试匹配滑动窗口。对窗口:

AAAA

计算摘要后,将窗口右移一个字节时,可以利用上一个窗口的部分计算结果,而不是从头重新计算整个窗口。这使源端能够在大文件中较快寻找旧块。

实际匹配通常包含两层判断:

  1. 弱校验和:计算快,用于筛选候选块;
  2. 强校验和:碰撞概率更低,用于确认候选块。

弱校验和发生碰撞并不意味着数据错误,因为还需要强校验和或后续校验来确认。具体摘要算法和可选算法会受到 rsync 协议版本、构建方式及参数影响,不能把某个固定摘要算法当作所有现代系统的永久保证。


2.3 一个完整的增量匹配算例

假设目标端旧文件按四字节分块:

目标端旧文件 T:

B1 = abcd
B2 = efgh
B3 = ijkl
B4 = mnop

源端新文件为:

源端新文件 S:

abcd
XXXX
ijkl
mnop
qrst

将其连续表示为:

abcdefghijklmnop   # 旧文件
abcdXXXXijklmnopqrst # 新文件

源端扫描新文件时可能得到:

  1. 在偏移 0 处发现 abcd,匹配 B1
  2. 接下来四字节是 XXXX,目标块中没有匹配,作为字面数据发送;
  3. 接下来发现 ijkl,匹配 B3
  4. 接下来发现 mnop,匹配 B4
  5. 最后的 qrst 没有对应目标块,作为字面数据发送。

传输逻辑可以写成:

COPY B1
LITERAL XXXX
COPY B3
COPY B4
LITERAL qrst

目标端据此生成:

abcdXXXXijklmnopqrst

这里有两个重要结论:

  • 文件中间插入数据时,旧数据块仍可能被复用;
  • “只传输变化部分”是一个尽量优化的结果,不是绝对保证。

如果文件很小、目标文件不存在、文件属性不适合增量处理,或者使用了 --whole-file,rsync 可能直接发送整个文件。


2.4 文件是否需要处理:快速检查与完整校验

默认情况下,rsync 通常先用文件的大小和修改时间判断文件是否需要更新。这种判断可表示为:

unchanged    (sizeS=sizeT)(mtimeS=mtimeT)\text{unchanged} \iff (\text{size}_S = \text{size}_T) \land (\text{mtime}_S = \text{mtime}_T)

实际行为还受到时间精度、协议能力和相关选项影响。

这种方法速度快,但存在边界:

  • 文件内容被修改后又恢复到相同大小;
  • 修改时间被程序保留或手工伪造;
  • 不同文件系统的时间戳精度不同;
  • 源端和目标端的时间戳语义存在差异。

需要按内容校验时,可以使用:

rsync -a -c /srv/data/ /backup/data/

-c--checksum 会读取文件内容并计算校验值,再决定文件是否需要更新。它会增加双方的磁盘读取和 CPU 开销,但它不等于“传输时关闭增量算法”。文件是否需要更新和更新时如何传输,是两个不同阶段:

  1. --checksum:更严格地判断内容是否相同;
  2. 增量传输:决定已变化文件如何减少网络传输。

如果网络很快、目标端文件完全不存在,或使用:

rsync -a --whole-file source/ destination/

则可以直接传输整个文件。--whole-file 会放弃块级增量传输,适合本机复制或网络带宽明显充足的情况,但不应机械地用于远程大文件同步。


三、从本机到远程主机:SSH、数据流和权限

典型的远程同步命令是:

rsync -a --info=progress2 /srv/data/ backup@example.net:/data/backup/

数据流大致如下:

sequenceDiagram
    participant S as 源端 rsync
    participant H as SSH
    participant D as 目标端 rsync

    S->>H: 建立 SSH 连接并启动远端 rsync
    S->>D: 发送文件列表与元数据
    D->>S: 返回目标端已有文件的块签名
    S->>D: 发送字面数据和可复用块引用
    D->>D: 写入临时文件并设置元数据
    D->>D: 完成后重命名为目标文件

SSH 负责:

  • 身份认证;
  • 加密传输;
  • 启动远端 rsync 进程;
  • 传输 rsync 协议数据。

SSH 并不替代 rsync 的文件比较和增量算法。远端账号必须满足:

  • 能读取源端对应数据,或能写入目标端对应目录;
  • 远端存在兼容的 rsync
  • SSH 账号的 shell、权限和路径配置允许启动远端程序。

例如,目标路径需要 root 权限时,可以这样指定远端 rsync 以 root 身份运行:

rsync -a \
  --rsync-path='sudo -n /usr/bin/rsync' \
  /srv/data/ backup@example.net:/var/backups/data/

前置条件是远端用户可以无密码执行指定的 sudo 命令。应使用精确的 sudoers 规则,而不是给备份账号开放任意 root shell。


四、-a 到底同步了什么

最常见的归档模式是:

rsync -a source/ destination/

-a 等价于:

-r -l -p -t -g -o -D

常见含义如下:

选项 含义
-r 递归进入目录
-l 保留符号链接本身
-p 保留权限模式
-t 保留修改时间
-g 保留组
-o 保留所有者
-D 保留设备文件和特殊文件

-a 不是“完整复制一切”。它默认不包含:

  • POSIX ACL;
  • 扩展属性;
  • 硬链接关系;
  • 通常意义上的访问时间;
  • 某些平台特有的创建时间或文件标志。

可按需要增加:

rsync -aHAX source/ destination/

其中:

  • -H:尽量保留硬链接关系;
  • -A:保留 POSIX ACL;
  • -X:保留扩展属性。

-HAX 可能显著增加文件列表内存和处理开销。对于包含大量硬链接、ACL 或 xattr 的系统树,必须在目标文件系统和挂载选项支持这些属性的前提下测试恢复。

4.1 所有者和组不是普通文件内容

如果目标端以普通用户运行:

rsync -a source/ destination/

即使命令没有报错,也未必能把目标文件所有者设置为源端所有者。常见结果是:

  • 文件内容同步成功;
  • 权限可能部分设置成功;
  • 所有者保持为执行 rsync 的目标账号;
  • 组只能设置为该账号有权使用的组。

需要跨系统保持 UID/GID 语义时,通常要考虑:

rsync -aHAX --numeric-ids source/ root@host:/destination/

--numeric-ids 表示按数字 UID/GID 处理,而不是依赖两端名称解析。它适合两端账号名称不同但数字 ID 具有既定含义的场景;如果两台机器的 UID/GID 规划不同,机械保留数字 ID 反而可能造成错误归属。

可以先用试运行检查结果:

rsync -aHAXnvi --numeric-ids source/ root@host:/destination/

这里:

  • -n:不真正修改;
  • -v:显示详细信息;
  • -i:显示变更项的 itemized 状态。

--dry-run 只能验证 rsync 计算出的操作计划,不能证明目标文件系统一定允许后续 chmod、chown、ACL 或 xattr 操作。


五、符号链接、硬链接、设备文件和特殊文件

5.1 符号链接

归档模式中的 -l 会复制符号链接本身,而不是默认跟随其指向内容:

ln -s /srv/data/current /backup-link

如果要跟随符号链接,需要明确使用相关选项,例如 -L。这可能改变同步边界:

  • 符号链接指向目录外部时,可能把外部内容纳入同步;
  • 出现循环或巨大目录树时,风险更高;
  • 恢复时不再保留原来的链接语义。

因此,备份系统目录或发布目录时,应先确认是想保存“链接关系”,还是想保存“链接指向的数据”。

5.2 硬链接

两个硬链接共享同一个 inode:

ln original copy

只使用 -a 时,目标端可能得到两个内容相同但 inode 不同的普通文件。使用:

rsync -aH source/ destination/

才会尝试保留硬链接关系。

-H 的意义不只是节省空间,也可能影响程序行为。例如某些队列、邮件存储或去重目录依赖硬链接表示状态。另一方面,处理大量硬链接会增加内存和计算成本。

5.3 设备文件与特殊文件

-D 包括设备文件和特殊文件,但创建它们通常需要 root 权限,并且目标文件系统必须适合保存这些对象。普通用户备份 /dev,往往只能得到不完整或不具备恢复意义的结果。

这也是“文件级备份”与“块设备镜像”不同的原因:文件级 rsync 适合文件树,不会自动复制文件系统内部所有元数据、空闲块、引导结构或数据库页的一致快照。


六、删除:目标端多出来的文件何时会消失

默认同步是单向覆盖,不删除目标端多余文件:

rsync -a /srv/data/ /backup/data/

如果源端删除了:

/srv/data/old.log

目标端的:

/backup/data/old.log

仍然存在。

要让目标端更接近源端,可以使用:

rsync -a --delete /srv/data/ /backup/data/

这会删除目标端文件列表中被判定为多余的对象。

6.1 --delete 的逻辑边界

--delete 不是“删除目标目录中所有源端没有的东西”这么简单。它受以下因素共同影响:

  • 源端实际生成的文件列表;
  • --exclude--include 规则;
  • 源端是否能正常读取目录;
  • 删除时机选项;
  • 目标端权限;
  • 是否启用备份;
  • 是否发生 I/O 错误。

被排除的目标文件通常不会因为普通 --delete 自动删除,因为它们不在同步文件列表中。--delete-excluded 会连排除项也纳入删除范围,风险明显更高:

rsync -a --delete --delete-excluded \
  --exclude='*.tmp' \
  /srv/data/ /backup/data/

这类命令不仅不复制 *.tmp,还可能删除目标端已有的 *.tmp

6.2 删除时机

rsync 提供不同删除时机,例如:

  • --delete-before:传输前删除;
  • --delete-during:传输过程中删除;
  • --delete-delay:延迟收集删除操作,后续执行;
  • --delete-after:传输后删除。

具体默认行为会受递归方式和 rsync 版本影响,因此脚本应明确指定所需语义,而不是依赖默认值。删除前执行:

rsync -a --delete --dry-run --itemize-changes \
  /srv/data/ /backup/data/

重点检查输出中的删除项,例如:

*deleting   old.log

--dry-run 不会删除文件,但它也不能保证正式执行时源端不会继续变化。

6.3 源端读取失败与误删除风险

假设源端某个目录因为挂载失败、权限变化或临时 I/O 错误而没有被完整读取。如果工具把这个不完整文件列表当作真实源状态,删除目标端对应文件就可能造成灾难。

rsync 对部分 I/O 错误有保护行为,但不能把这种保护当作备份策略。生产同步应:

  1. 先验证源路径确实已挂载;
  2. 检查源目录可读;
  3. 先执行 --dry-run
  4. 监控 rsync 退出码;
  5. 对删除启用备份目录或使用快照;
  6. 必要时使用 --ignore-errors 前先理解它会削弱删除保护,不能随意添加。

可以限制一次最多删除的数量:

rsync -a --delete --max-delete=100 \
  /srv/data/ /backup/data/

超过限制时命令会失败或停止删除;这适合作为异常变化的保险丝,但不是替代源端健康检查的方法。


七、断点和中断:rsync 如何继续未完成传输

7.1 普通重试本身就具有增量特性

假设大文件传输到一半时 SSH 断开:

rsync -a /srv/data/ backup@example.net:/backup/data/

默认情况下,未完成的临时文件可能在 rsync 退出时被删除。再次运行时,rsync 会重新传输整个目标文件,因为目标端没有一个可供继续使用的完整目标文件。

如果希望保留未完成文件,使用:

rsync -a --partial /srv/data/ backup@example.net:/backup/data/

--partial 保留部分传输文件。再次运行时,rsync 可能利用这份部分文件进行增量传输。

常见的进度写法是:

rsync -aP /srv/data/ backup@example.net:/backup/data/

-P 等价于:

--partial --progress

它适合人工执行,但在自动化脚本中最好明确写出选项,便于审计。

7.2 --partial-dir:把临时文件集中管理

可以指定部分文件目录:

rsync -a \
  --partial-dir=.rsync-partial \
  /srv/data/ backup@example.net:/backup/data/

这样未完成文件放在目标目录下的 .rsync-partial 中,而不是直接以最终文件名暴露。优点包括:

  • 正常目录中较少出现半成品;
  • 可以单独清理过期部分文件;
  • 目标目录的业务程序较少误读临时文件。

该目录需要目标端账号可写。部分文件目录的权限和清理策略也必须纳入备份空间管理。

7.3 --append--append-verify 不是通用断点续传

对于持续追加的日志或归档文件,可以使用:

rsync -a --append-verify source.log backup:/backup/

其前提是:

  • 目标文件是源文件的前缀;
  • 源文件只在末尾追加;
  • 旧前缀没有被修改或截断。

--append 更偏向于信任目标端已有前缀;如果目标端文件曾损坏或源文件发生覆盖,可能保留错误数据。--append-verify 会在追加前后进行更严格的验证,代价是更多读取。

对普通会被随机修改的数据库文件、虚拟磁盘或压缩包,不应因为“想要断点”就使用 --append。这类文件不满足“目标是源的正确前缀”的条件。

7.4 --inplace 的一致性风险

默认情况下,rsync 通常先写临时文件,成功后再将其重命名为最终文件。这种方式的好处是读者不容易看到一个正在拼接的半成品。

rsync -a source/ destination/

--inplace 则直接修改目标文件:

rsync -a --inplace source/ destination/

它可能减少额外磁盘空间需求,也适合某些大文件场景,但风险是:

  • 传输中断时,目标文件可能已经被部分改写;
  • 业务程序可能读到混合的新旧内容;
  • 与硬链接文件结合时,修改可能影响同 inode 的其他路径;
  • 与备份、快照和稀疏文件行为结合时需要额外验证。

除非明确理解这些后果,否则不要把 --inplace 当作普通同步的性能开关。


八、备份策略:同步副本不等于备份

纯同步命令:

rsync -a --delete /srv/data/ /backup/data/

得到的是一个会随源端删除而删除的镜像。源文件误删、勒索软件加密或错误部署后,下一次同步可能把错误状态复制到目标端。

因此,需要区分:

  • 镜像(mirror):目标尽量等于源;
  • 备份(backup):保留过去的状态,以便回滚;
  • 快照(snapshot):记录某个时间点的文件系统视图;
  • 归档(archive):长期保存、通常不再频繁修改的副本。

8.1 使用 --backup 保留被替换或删除的文件

rsync -a --delete \
  --backup \
  --backup-dir=/backup/.deleted/2025-03-08 \
  /srv/data/ /backup/data/

在目标端:

  • 将被新版本替换的文件移入备份目录;
  • --delete 下被删除的目标文件也可以移入备份目录;
  • 目标文件随后才按当前源端状态更新或删除。

--backup-dir 的路径解释与 rsync 版本及路径形式有关。使用绝对路径最容易审计;使用相对路径时,应明确它是相对于目标端对应目录解释,而不是相对于本地 shell 当前目录。

可以加上备份后缀:

rsync -a --backup \
  --suffix='.'$(date +%F-%H%M%S) \
  /srv/data/ /backup/data/

但仅靠时间后缀会带来目录膨胀,且同一秒内运行多次可能产生管理问题。更可靠的方案是由外部调度器创建唯一时间目录,并定义保留周期。

8.2 --backup-dir 的边界

它不是完整版本控制系统,也不自动提供:

  • 目录树的原子快照;
  • 去重;
  • 加密;
  • 异地复制;
  • 保留周期清理;
  • 恢复演练;
  • 防止攻击者同时删除备份的隔离性。

如果目标备份目录长期可被源端服务账号写入,攻击者取得该账号后可能删除或篡改所有副本。因此生产备份通常还需要:

  • 不同凭据;
  • 备份端最小权限;
  • 异地或离线副本;
  • 文件系统快照或对象存储版本;
  • 定期恢复测试。

九、用 --link-dest 制作节省空间的多版本目录

如果目标文件没有变化,可以使用硬链接让多个时间点共享 inode:

today=/backup/snapshots/2025-03-08
previous=/backup/snapshots/2025-03-07

mkdir -p "$today"

rsync -aH \
  --link-dest="$previous" \
  /srv/data/ "$today"/

如果 today 中某文件与 previous 中对应文件满足 rsync 的比较条件,目标可以创建硬链接,而不是重新复制数据。变化文件则写入新内容。

这形成类似:

snapshots/
├── 2025-03-07/
└── 2025-03-08/

删除一个快照目录中的硬链接,不会删除仍被其他快照引用的数据;只有最后一个硬链接被删除时,数据块才真正释放。

--link-dest 有几个边界:

  1. 它依赖目标文件系统支持硬链接;
  2. 源端和目标端必须有适合比较的属性;
  3. 若运行过程中源端变化,当前快照仍可能不是一致视图;
  4. 不能把硬链接快照当作不可变备份;
  5. 备份目录必须防止业务进程直接修改共享 inode 内容。

对数据库、虚拟机磁盘等不断写入的大文件,先创建 LVM、ZFS、Btrfs 或存储阵列快照,再对快照路径执行 rsync,通常比直接读取活动文件更容易得到一致状态。但快照能力取决于文件系统、卷管理器和应用本身,rsync 不会自动创建这些快照。


十、排除规则:同步边界必须明确

例如只同步源码和配置,不同步构建产物:

rsync -a \
  --exclude='.git/' \
  --exclude='build/' \
  --exclude='*.tmp' \
  /srv/project/ /backup/project/

排除规则不仅决定“复制什么”,还影响:

  • 文件列表;
  • --delete 的删除范围;
  • --backup 能否接管被删除对象;
  • 目录是否继续递归;
  • 末尾斜杠和模式匹配位置。

执行前应使用:

rsync -a -nvi \
  --delete \
  --exclude='.git/' \
  --exclude='build/' \
  /srv/project/ /backup/project/

不要只看“将复制哪些文件”,还要检查:

  • *deleting 行;
  • 被排除的目录是否导致预期文件消失;
  • 规则是否匹配了比预期更大的范围;
  • 源端路径是否正确。

当规则较多时,放入文件并显式指定:

rsync -a \
  --exclude-from=/etc/rsync/project.exclude \
  /srv/project/ /backup/project/

排除规则文件应纳入配置管理,否则未来恢复时可能无法解释某些文件为何从未进入备份。


十一、不要把 rsync 当作一致性快照工具

rsync 会遍历目录、读取文件并传输数据。源端文件在这个过程中继续变化时,可能出现:

1. rsync 先读取文件前半部分;
2. 应用修改文件后半部分;
3. rsync 再读取文件后半部分;
4. 目标端得到一个从未真实存在过的混合版本。

对于静态文件,这通常影响较小;对于以下对象则风险明显:

  • SQLite 数据库;
  • 正在写入的日志;
  • 虚拟机磁盘;
  • 邮件队列;
  • 应用上传中的大文件;
  • 多文件之间需要事务一致性的目录。

解决路径不是增加 -c 就够了,因为 -c 主要改变比较方式,不能让多个文件在同一时间被冻结。可采用:

  1. 让应用执行一致性导出;
  2. 停止或暂停写入;
  3. 对底层卷创建快照;
  4. 从快照目录执行 rsync;
  5. 恢复后进行应用级校验。

例如,数据库备份应优先使用数据库提供的 dump 或在线备份机制,再用 rsync 同步 dump 文件,而不是直接复制活动数据库文件。


十二、错误处理、退出码和验证

rsync 的命令输出不是唯一的成功标准。自动化脚本至少要检查退出码:

set -u

if rsync -a --delete --backup \
    --backup-dir=/backup/.deleted/"$(date +%F-%H%M%S)" \
    /srv/data/ /backup/data/
then
    echo "sync succeeded"
else
    status=$?
    echo "sync failed, exit code=$status" >&2
    exit "$status"
fi

常见错误类型包括:

  • SSH 认证失败;
  • 远程 rsync 不存在;
  • 目标目录无写权限;
  • 磁盘空间不足;
  • 文件系统不支持所需属性;
  • 源端路径未挂载;
  • 传输中断;
  • 某些文件读取失败;
  • 文件名或 shell 参数引用错误。

rsync 的退出码可能表示成功、部分成功、语法错误、连接失败或其他错误;不同版本的具体代码表应以目标系统的 man rsync 为准。不要把“输出中出现了某几行”作为脚本成功判据。

12.1 查看计划而不修改目标

rsync -aHAXnvi \
  --delete \
  /srv/data/ /backup/data/

重点:

  • -n:只计算计划;
  • -i:显示属性变化;
  • -v:增加可读信息。

正式执行前,可先验证目标挂载点:

mountpoint -q /backup || {
    echo "/backup is not mounted" >&2
    exit 1
}

这是防止“备份盘未挂载,rsync 却把数据写到根文件系统普通目录”的关键检查。若随后使用 --delete,错误挂载点还可能导致更严重的错误操作。

12.2 同步后验证内容和元数据

针对内容:

rsync -aHAXnc \
  /srv/data/ /backup/data/

如果没有需要更新的文件,说明按当前比较规则双方一致。-c 会读取两端文件内容,耗时可能较长。

针对抽样校验:

sha256sum /srv/data/example.iso
sha256sum /backup/data/example.iso

针对权限、所有者和链接:

stat /srv/data/example.iso
stat /backup/data/example.iso
getfacl /srv/data/example.iso
getfattr -d /srv/data/example.iso

不同系统未必安装 getfaclgetfattr;命令不可用不等于对象不存在,必须先安装相应工具或使用系统提供的属性检查方式。


十三、权限和删除结合时的典型失败路径

场景一:普通用户运行归档模式

rsync -a /srv/data/ /backup/data/

可能发生:

  1. 文件内容复制成功;
  2. 时间戳设置成功;
  3. 某些权限设置成功;
  4. 所有者和特殊文件无法恢复;
  5. rsync 返回非零退出码或输出权限错误。

如果脚本只检查目标文件是否存在,就会误判为成功备份。

场景二:目标目录权限过严

目标文件存在,但 rsync 无法覆盖或重命名:

permission denied
failed to set times
unable to delete

默认临时文件加重命名的流程还需要目标目录本身可写,不仅要求目标文件可写。即使目标文件权限允许写入,如果目录不允许创建或重命名,也可能失败。

场景三:源端目录突然消失

例如 /srv/data 原本是挂载点,备份前挂载失败,实际变成一个空目录。若直接执行:

rsync -a --delete /srv/data/ /backup/data/

可能得到一个“看起来成功”的空源同步计划,并删除目标端大量文件。挂载检查、--dry-run、删除备份目录和快照是针对该风险的不同防线。


十四、硬链接、稀疏文件和大文件的取舍

14.1 稀疏文件

稀疏文件的逻辑大小很大,但中间可能包含大量未分配的零块。同步时可考虑:

rsync -aS source/ destination/

-S--sparse 尝试在目标端保留稀疏结构。它适合虚拟磁盘等文件,但必须验证目标文件系统和后续程序行为。

逻辑大小相同不代表物理占用相同:

stat --format='size=%s blocks=%b' image.raw

这里 size 是逻辑字节数,blocks 是实际分配块数的系统报告值。备份容量规划不能只看逻辑大小。

14.2 大文件与带宽

大文件发生少量中间修改时,块级算法可能节省网络流量,但仍需要:

  • 读取源端文件;
  • 读取目标端文件并生成签名;
  • 进行块匹配;
  • 写出新文件或原地更新。

因此,网络流量减少不代表磁盘 I/O 和 CPU 同样减少。对于本机快速存储之间的复制,--whole-file 有时更合适;对于高延迟或低带宽链路,增量算法更有价值。实际选择应通过测试和监控决定,而不是凭参数名称推断性能。


十五、一个较安全的生产同步流程

下面的流程适合“源端目录同步到备份盘,并保留被删除文件”的场景:

第一步:确认目标挂载

mountpoint -q /backup

失败时立即退出,不执行 rsync。

第二步:确认源端不是空的错误挂载点

test -d /srv/data
findmnt -T /srv/data

如果 /srv/data 预期应是独立文件系统,检查 findmnt 的结果是否符合预期。

第三步:执行试运行

rsync -aHAXnvi --delete \
  --backup \
  --backup-dir=/backup/.deleted/2025-03-08 \
  /srv/data/ /backup/data/

检查复制项、属性变化和删除项。

第四步:正式执行

mkdir -p /backup/.deleted/2025-03-08

rsync -aHAX \
  --delete \
  --backup \
  --backup-dir=/backup/.deleted/2025-03-08 \
  --partial-dir=.rsync-partial \
  /srv/data/ /backup/data/

这里的作用是:

  • -aHAX:尽量保存目录树、硬链接、ACL 和扩展属性;
  • --delete:删除目标端源端不存在的对象;
  • --backup:删除或替换前先保留旧对象;
  • --partial-dir:中断时保留部分传输状态。

第五步:检查退出码和日志

status=$?
if [ "$status" -ne 0 ]; then
    echo "rsync failed: $status" >&2
    exit "$status"
fi

实际脚本还应记录:

  • 开始和结束时间;
  • 源端和目标端挂载信息;
  • rsync 完整输出;
  • 删除数量;
  • 目标剩余空间;
  • 异常退出码。

第六步:定期恢复演练

从备份中恢复到隔离目录,而不是只检查“文件存在”:

mkdir -p /restore-test
rsync -aHAX /backup/data/ /restore-test/

然后验证:

  • 文件内容;
  • 权限和所有者;
  • ACL、xattr;
  • 符号链接;
  • 硬链接关系;
  • 应用是否能启动;
  • 数据库是否能通过一致性检查。

没有恢复验证的同步任务,只证明“某些数据曾经写入目标端”,不能证明备份可用。


十六、常见误解和对应边界

误解一:-a 就是完整系统备份

不准确。-a 不自动包含 ACL、xattr 和硬链接,也不复制文件系统级元数据、引导信息、快照状态或数据库一致性状态。根据需求增加 -HAX,仍然不等于块设备镜像。

误解二:rsync 只传输变化的字节

这是目标而不是无条件保证。首次复制、目标文件不存在、使用 --whole-file 或算法无法有效复用时,可能传输整个文件。即使只传输变化数据,也可能读取双方完整文件。

误解三:再次运行一定能从中断位置续传

普通重试通常能避免重新传输已经匹配的完整块,但未完成临时文件默认可能被删除。要保留它,应使用 --partial--partial-dir--append 仅适用于严格追加模型。

误解四:--delete 只是清理垃圾

--delete 是破坏性操作。源端路径错误、挂载失败、排除规则错误或源端读取不完整,都可能导致目标端不应删除的文件被删除。

误解五:同步成功就代表备份成功

同步成功只表示该次 rsync 基本完成,不能证明:

  • 源端数据当时是一致的;
  • 元数据全部保存;
  • 备份没有被篡改;
  • 历史版本仍存在;
  • 能够恢复并运行应用。

真正的备份策略需要版本、隔离、保留周期、监控和恢复演练共同组成。


十七、命令选择的最小决策表

目标 常用起点 关键风险
普通目录镜像 rsync -a src/ dst/ 不删除目标多余文件
严格镜像 rsync -a --delete src/ dst/ 删除错误路径中的数据
保存 ACL/xattr/硬链接 rsync -aHAX src/ dst/ 权限、文件系统和资源开销
中断后保留部分文件 --partial--partial-dir 部分文件可能被误读
只追加的大文件 --append-verify 不适合随机修改文件
保留删除和替换版本 --backup --backup-dir=... 需要清理和隔离
多时间点节省空间 --link-dest 硬链接不是不可变快照
严格比较内容 --checksum 增加全量读取开销
预览计划 --dry-run --itemize-changes 不能预测源端后续变化

rsync 的核心能力可以概括为:用文件列表确定同步边界,用元数据规则决定对象属性,用块匹配减少传输,用删除和备份选项决定目标端历史如何处理。当它与一致性快照、应用级导出、权限设计、异地副本和恢复演练组合使用时,才适合承担可靠备份体系中的文件同步部分。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。