rsync 增量同步两个大目录:断点续传与完整校验指南

讲解如何用 rsync 串行增量复制两个大型目录,安全预演、记录日志、断点续传,并通过二次 dry-run、校验和、文件数量和权限检查确认复制成功。

使用 rsync 复制两个大型目录时,最稳妥的做法不是把两个源路径随手写进一条命令,而是先明确每个源目录对应哪个目标目录,再逐组执行:

1
2
/mnt/disk1/photos/   → /mnt/disk2/photos/
/mnt/disk1/archive/  → /mnt/disk2/archive/

每组任务都先用 --dry-run 预演,正式复制时保留未完成文件并记录日志,复制结束后再执行一次只读比对。

如果普通比对没有输出,说明 rsync 按文件大小、修改时间和相关属性没有发现差异;如果要确认文件内容也一致,再增加 --checksum 做全量读取校验。

可以直接采用的安全方案

下面假设源盘和目标盘都已正确挂载:

1
2
3
4
SRC1=/mnt/disk1/photos
DST1=/mnt/disk2/photos
SRC2=/mnt/disk1/archive
DST2=/mnt/disk2/archive

先创建目标目录:

1
sudo mkdir -p "$DST1" "$DST2"

分别预演两组同步:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sudo rsync -aHAXn \
  --numeric-ids \
  --itemize-changes \
  "$SRC1/" \
  "$DST1/"

sudo rsync -aHAXn \
  --numeric-ids \
  --itemize-changes \
  "$SRC2/" \
  "$DST2/"

确认源、目标路径和变更列表都正确后,去掉 -n,串行执行正式同步:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
sudo rsync -aHAX \
  --numeric-ids \
  --partial \
  --partial-dir=.rsync-partial \
  --info=progress2,stats2 \
  --log-file=/var/log/rsync-photos.log \
  "$SRC1/" \
  "$DST1/"

sudo rsync -aHAX \
  --numeric-ids \
  --partial \
  --partial-dir=.rsync-partial \
  --info=progress2,stats2 \
  --log-file=/var/log/rsync-archive.log \
  "$SRC2/" \
  "$DST2/"

第一次迁移建议不要添加 --delete

这样即使目标目录中有额外文件,rsync 也不会删除它们。等目录结构和校验结果都确认无误后,再决定是否需要把目标做成源的完全镜像。

为什么两个目录最好分两次运行

rsync 的最后一个位置参数是目标目录,前面的参数都被视为源。

下面这种写法:

1
rsync -a /source/a/ /source/b/ /target/

表示把两个源都汇入同一个 /target/,并不表示 /source/a/ 对应 /target/a//source/b/ 对应 /target/b/

当两个目录需要分别落到两个目标路径时,应执行两次 rsync,或者写一个明确的串行脚本。

分开运行还有几个实际好处:

  • 每个目录有独立日志和退出状态;
  • 某一组失败时,不会掩盖另一组的结果;
  • 校验时可以逐组定位差异;
  • 可以让机械硬盘顺序读写,避免两个任务争抢磁头;
  • 能为不同目录设置不同的排除规则。

如果两个源确实要汇入同一个目标,先确认它们是否包含同名相对路径。

例如两个源都有 2026/report.pdf,后执行的源可能覆盖先执行的同名文件。此时更安全的做法是为它们保留独立子目录。

先确认挂载点,避免复制到系统盘

大目录迁移最危险的错误之一,是目标磁盘没有挂载成功,但目标路径仍然存在。

此时 rsync 会把数据写进根文件系统中的普通目录,直到系统盘被写满。

开始前检查块设备、文件系统和挂载点:

1
2
3
4
5
6
lsblk -o NAME,SIZE,FSTYPE,UUID,MOUNTPOINTS
findmnt -T /mnt/disk1/photos
findmnt -T /mnt/disk2/photos
findmnt -T /mnt/disk1/archive
findmnt -T /mnt/disk2/archive
df -hT /mnt/disk1 /mnt/disk2

重点确认:

  1. /mnt/disk1/mnt/disk2 对应预期设备;
  2. 目标盘可用空间足够;
  3. 目标挂载不是只读状态;
  4. 两个路径不是意外指向同一个文件系统位置;
  5. 目标路径不是源路径内部的子目录。

可以用下面的命令查看解析后的真实路径:

1
2
3
4
readlink -f "$SRC1"
readlink -f "$DST1"
readlink -f "$SRC2"
readlink -f "$DST2"

再做一个小型写入测试:

1
2
3
4
5
sudo touch "$DST1/.rsync-write-test"
sudo rm "$DST1/.rsync-write-test"

sudo touch "$DST2/.rsync-write-test"
sudo rm "$DST2/.rsync-write-test"

这里只删除刚刚明确创建的测试文件,不要用通配符清理目标目录。

目录末尾斜杠决定复制层级

这两条命令的结果不同:

1
2
rsync -a /source/ /target/
rsync -a /source  /target/

第一条复制 /source/ 里面的内容:

1
2
/target/file1
/target/subdir/file2

第二条复制 source 目录本身:

1
2
/target/source/file1
/target/source/subdir/file2

同步两个已经建立好的对应目录时,通常在源路径和目标路径后都写 /

1
rsync -a "$SRC1/" "$DST1/"

不要只凭目录名称猜测结果。第一次执行前用 --dry-run --itemize-changes 查看将创建的相对路径。

推荐参数分别解决什么问题

基础参数组合是:

1
-aHAX

其中:

参数 作用 注意事项
-a 归档模式,递归并保留常见属性 不等于自动保留所有硬链接、ACL 和扩展属性
-H 保留硬链接关系 大量硬链接会增加内存消耗
-A 保留 POSIX ACL 目标文件系统必须支持 ACL
-X 保留扩展属性 目标文件系统必须支持 xattr
--numeric-ids 直接保留 UID、GID 数值 适合 Linux 磁盘迁移和系统数据复制
--partial 中断后保留未传完的文件 下次运行仍会检查并继续传输
--partial-dir=.rsync-partial 把未完成文件放进专用目录 校验完成前不要手工删除该目录
--info=progress2 显示整个任务的总体进度 统计总量会随文件列表生成方式变化
--info=stats2 结束时显示传输统计 不能替代复制后的独立校验
--log-file 把详细记录写入日志 日志目录必须可写

如果复制的是普通文档、照片和视频,而且源目录没有硬链接、ACL 或扩展属性,可以按实际需求减少参数。

不要机械地认为参数越多越安全。

例如目标是 FAT、exFAT 或某些网络文件系统时,权限、所有者、ACL、xattr 可能无法完整保存,-A-X 或所有者设置可能报错。

先查看两端文件系统类型:

1
2
findmnt -no FSTYPE -T "$SRC1"
findmnt -no FSTYPE -T "$DST1"

如果两端能力不同,应先明确需要保留的是文件内容,还是完整的 Linux 元数据。

增量同步到底会复制什么

默认情况下,rsync 主要根据文件大小和修改时间判断文件是否需要更新。

再次运行相同命令时:

  • 新文件会被复制;
  • 大小或修改时间变化的文件会被更新;
  • 没有变化的文件会被跳过;
  • 目标独有文件会保留;
  • 中断的文件会按照当前状态重新检查并继续处理。

这就是增量同步适合大目录迁移的原因。

但它不是文件系统快照。

如果源目录在复制过程中持续变化,最后得到的目标可能包含不同时间点的文件状态。普通照片库或归档目录可以在首次复制后再运行一次增量补齐;数据库、虚拟机镜像和正在写入的应用数据应先停止服务、冻结写入或使用文件系统快照。

预演时怎样阅读变更列表

推荐的预演命令是:

1
2
3
4
5
sudo rsync -aHAXn \
  --numeric-ids \
  --itemize-changes \
  "$SRC1/" \
  "$DST1/"

-n--dry-run 的短写,只计算计划,不写入文件。

--itemize-changes 会显示每个变化项及原因。常见输出形式包括:

1
2
3
4
>f+++++++++ new-file.iso
>f..t...... changed-time.log
.d...p..... existing-directory/
*deleting   old-file.tmp

大致可以这样理解:

  • >f+++++++++:目标中没有该文件,将新建;
  • >f 开头且后面有属性标记:文件内容或属性将更新;
  • .d:目录本身的属性需要调整;
  • *deleting:配合 --delete 时,目标独有内容将被删除。

如果预演中出现意外的顶层目录名,通常是源路径末尾 / 写错。

如果几乎所有大文件都显示要重传,应检查两端时间、文件系统时间精度、挂载参数,以及文件是否被应用持续改写。

用脚本串行同步两个目录

需要重复运行时,可以保存为 /usr/local/sbin/sync-two-dirs.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env bash
set -Eeuo pipefail

sync_one() {
  local name=$1
  local source=$2
  local target=$3
  local log_file="/var/log/rsync-${name}.log"

  printf '开始同步 %s: %s -> %s\n' "$name" "$source" "$target"

  rsync -aHAX \
    --numeric-ids \
    --partial \
    --partial-dir=.rsync-partial \
    --info=progress2,stats2 \
    --log-file="$log_file" \
    "$source/" \
    "$target/"

  printf '完成同步 %s\n' "$name"
}

sync_one photos /mnt/disk1/photos /mnt/disk2/photos
sync_one archive /mnt/disk1/archive /mnt/disk2/archive

赋予执行权限并运行:

1
2
sudo chmod 750 /usr/local/sbin/sync-two-dirs.sh
sudo /usr/local/sbin/sync-two-dirs.sh

脚本使用 set -Eeuo pipefail

如果第一组 rsync 返回非零状态,脚本会停止,不会把整体任务误报为成功。处理完错误后重新运行即可,已经一致的文件会被跳过。

在 tmux 中运行长时间任务

数 TB 数据可能需要数小时甚至数天。推荐在 tmux 中执行,而不是依赖一直打开的 SSH 窗口:

1
tmux new -s rsync-copy

进入会话后运行:

1
sudo /usr/local/sbin/sync-two-dirs.sh

Ctrl+b,再按 d,可以让会话在后台继续。

重新连接:

1
tmux attach -t rsync-copy

同时可以从另一个终端观察日志:

1
2
sudo tail -f /var/log/rsync-photos.log
sudo tail -f /var/log/rsync-archive.log

检查进程:

1
pgrep -af rsync

不要同时启动多个相同脚本。两个任务写入同一块机械硬盘时,并行复制通常只会增加随机寻道和等待时间。

如何确认 rsync 本身正常结束

命令结束后首先检查退出状态:

1
echo $?

0 表示本次运行没有检测到错误,但不能单独证明每个文件内容都经过完整校验。

常见非零状态包括:

状态码 常见含义 处理方向
11 文件 I/O 错误 查看内核日志、磁盘和文件系统状态
12 rsync 协议数据流错误 检查远端连接、版本和日志
23 部分文件未传输 搜索权限、消失文件、xattr 和 I/O 报错
24 源文件在传输中消失 判断是否有程序正在移动或删除文件
30 数据传输超时 检查网络、SSH 和超时参数

查看日志末尾:

1
2
sudo tail -n 100 /var/log/rsync-photos.log
sudo tail -n 100 /var/log/rsync-archive.log

不要只看终端最后出现了“总字节数”。日志中如果存在 IO errorPermission deniedfailedvanished file,仍需判断是否影响验收。

第一层校验:再次执行 dry-run

正式复制后,对两组目录分别运行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sudo rsync -aHAXn \
  --numeric-ids \
  --delete \
  --itemize-changes \
  "$SRC1/" \
  "$DST1/"

sudo rsync -aHAXn \
  --numeric-ids \
  --delete \
  --itemize-changes \
  "$SRC2/" \
  "$DST2/"

这里同时使用了 -n,所以 --delete 只会报告目标端多余内容,不会真的删除。

没有输出通常表示:

  • rsync 没有发现需要新增或更新的文件;
  • 目标端没有源端不存在的额外路径;
  • 本次要求保留的常见属性没有差异。

如果输出只有 .rsync-partial/,先确认没有未完成任务。所有同步和校验都结束后,再决定是否清理该临时目录。

如果输出包含 *deleting,说明目标中有源目录没有的文件。先核对这些文件是否应该保留,不要立刻去掉 -n

第二层校验:读取双方内容并计算校验和

普通 dry-run 主要依赖大小和修改时间,不能排除“大小和时间相同但内容不同”的少见情况。

严格校验可以使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sudo rsync -aHAXnc \
  --numeric-ids \
  --delete \
  --itemize-changes \
  "$SRC1/" \
  "$DST1/"

sudo rsync -aHAXnc \
  --numeric-ids \
  --delete \
  --itemize-changes \
  "$SRC2/" \
  "$DST2/"

其中 -c 等同于 --checksum

它会读取双方文件内容并计算校验和,而不是仅比较大小和修改时间。因为同时带有 -n,发现差异时只报告,不修正,也不会删除文件。

对数 TB 数据执行 -c 可能需要很长时间,并对源盘和目标盘产生一轮完整读取负载。

适合使用严格校验的场景包括:

  • 原磁盘出现过读错误或掉盘;
  • 迁移经过不稳定网络;
  • 文件内容非常重要且没有其他校验机制;
  • 需要在下线源盘前完成最终验收;
  • 怀疑目标文件系统或内存导致静默损坏。

如果磁盘健康状况不佳,先检查 SMART 和内核日志,不要为了校验反复读取濒临故障的磁盘。

第三层校验:比较文件数量和逻辑字节数

统计普通文件数量:

1
2
3
4
5
find "$SRC1" -xdev -type f -printf '.' | wc -c
find "$DST1" -xdev -type f -printf '.' | wc -c

find "$SRC2" -xdev -type f -printf '.' | wc -c
find "$DST2" -xdev -type f -printf '.' | wc -c

统计普通文件的逻辑大小总和:

1
2
3
4
5
find "$SRC1" -xdev -type f -printf '%s\n' | awk '{sum += $1} END {printf "%.0f\n", sum}'
find "$DST1" -xdev -type f -printf '%s\n' | awk '{sum += $1} END {printf "%.0f\n", sum}'

find "$SRC2" -xdev -type f -printf '%s\n' | awk '{sum += $1} END {printf "%.0f\n", sum}'
find "$DST2" -xdev -type f -printf '%s\n' | awk '{sum += $1} END {printf "%.0f\n", sum}'

-xdev 表示不进入目录下挂载的其他文件系统。

如果源目录本来就包含嵌套挂载点,而这些内容也应该复制,就不能直接使用 -xdev,并且要确认 rsync 的挂载点处理策略符合预期。

文件数量和逻辑字节数相等只能作为辅助证据,不能证明路径和内容逐一一致。

du -sh 更不能作为唯一验收依据,因为稀疏文件、压缩、去重、块大小、硬链接和文件系统元数据都会影响实际占用空间。

生成 SHA-256 清单做独立校验

如果希望保存可审计的内容清单,可以在每个目录内部按相对路径计算 SHA-256:

1
2
3
4
5
6
7
cd "$SRC1"
find . -type f -print0 | sort -z | xargs -0 -r sha256sum > /tmp/photos-source.sha256

cd "$DST1"
find . -type f -print0 | sort -z | xargs -0 -r sha256sum > /tmp/photos-target.sha256

diff -u /tmp/photos-source.sha256 /tmp/photos-target.sha256

第二组目录同样处理:

1
2
3
4
5
6
7
cd "$SRC2"
find . -type f -print0 | sort -z | xargs -0 -r sha256sum > /tmp/archive-source.sha256

cd "$DST2"
find . -type f -print0 | sort -z | xargs -0 -r sha256sum > /tmp/archive-target.sha256

diff -u /tmp/archive-source.sha256 /tmp/archive-target.sha256

diff 没有输出且退出状态为 0,表示两份清单一致。

这仍然只校验普通文件内容和相对路径,不验证目录权限、ACL、所有者、硬链接关系或扩展属性。完整 Linux 数据迁移应把它与 rsync -aHAXnc 的结果结合起来看。

检查权限、ACL、扩展属性和硬链接

抽查关键文件的基本属性:

1
2
stat "$SRC1/关键文件"
stat "$DST1/关键文件"

检查 ACL:

1
2
getfacl -p "$SRC1/关键目录"
getfacl -p "$DST1/关键目录"

检查扩展属性:

1
2
getfattr -d -m- "$SRC1/关键文件"
getfattr -d -m- "$DST1/关键文件"

检查硬链接文件的 inode 和链接数:

1
2
ls -li "$SRC1/路径/文件1" "$SRC1/路径/文件2"
ls -li "$DST1/路径/文件1" "$DST1/路径/文件2"

源端和目标端的 inode 数字不需要相同,但同一端内原本属于一组硬链接的文件,应该共享 inode,并保留正确的链接数。

什么时候才使用 –delete

--delete 让目标最终与源保持镜像关系:目标中存在、源中不存在的路径会被删除。

先做只读预演:

1
2
3
4
5
6
sudo rsync -aHAXn \
  --numeric-ids \
  --delete-delay \
  --itemize-changes \
  "$SRC1/" \
  "$DST1/"

注意:--delete-delay 也会启用删除语义,只是把实际删除推迟到传输结束阶段。

正式镜像前至少确认:

  1. 源和目标没有写反;
  2. 目标挂载点确实在线;
  3. dry-run 中的每个 *deleting 都符合预期;
  4. 目标端没有需要独立保留的文件;
  5. 已有另一份可恢复备份。

如果希望删除仍可回看,可以先使用备份目录:

1
2
3
4
5
6
7
8
sudo rsync -aHAX \
  --numeric-ids \
  --delete-delay \
  --backup \
  --backup-dir="/mnt/disk2/rsync-deleted-$(date +%F)" \
  --itemize-changes \
  "$SRC1/" \
  "$DST1/"

备份目录必须位于空间足够的位置,并且不能放进会再次被同步的源目录树中。

排除回收站和临时目录

NAS 或桌面磁盘中常见一些不需要迁移的缓存目录。仅在确认它们确实可丢弃后添加排除规则:

1
2
3
4
5
6
7
8
9
sudo rsync -aHAX \
  --numeric-ids \
  --partial \
  --info=progress2,stats2 \
  --exclude='#recycle/' \
  --exclude='@eaDir/' \
  --exclude='.Trash-*/' \
  "$SRC1/" \
  "$DST1/"

'#recycle/' 必须加引号,否则 Shell 可能把 # 后面的内容视为注释。

正式命令和校验命令必须使用相同的排除规则,否则校验时会把有意忽略的内容报告为缺失。

排除规则较多时,保存到文件:

1
2
3
#recycle/
@eaDir/
.Trash-*/

然后使用:

1
2
3
4
5
rsync -aHAXn \
  --exclude-from=/etc/rsync-large-dirs.exclude \
  --itemize-changes \
  "$SRC1/" \
  "$DST1/"

控制磁盘和网络负载

在生产服务器上迁移大目录时,可以降低进程的 CPU 和 I/O 优先级:

1
2
3
4
5
6
7
sudo nice -n 10 ionice -c2 -n7 \
  rsync -aHAX \
  --numeric-ids \
  --partial \
  --info=progress2,stats2 \
  "$SRC1/" \
  "$DST1/"

跨网络传输还可以限速。远端目标建议直接写出绝对路径,避免混淆本地变量和远端路径:

1
2
3
4
5
6
rsync -aHAX \
  --partial \
  --info=progress2,stats2 \
  --bwlimit=80M \
  "$SRC1/" \
  user@backup-host:/mnt/disk2/photos/

本地磁盘到本地磁盘通常不需要 -z 压缩。照片、视频、压缩包也很难进一步压缩,开启 -z 只会增加 CPU 负担。

常见失败与处理方法

Permission denied

先在日志中确认是读取源文件失败、创建目标文件失败,还是设置所有者、ACL、xattr 失败。

1
sudo grep -Ei 'permission denied|operation not permitted|failed' /var/log/rsync-photos.log

不要直接对整个目标执行宽泛的 chmod -R 777。应检查挂载选项、目标目录所有者和目标文件系统能力。

No space left on device

同时检查容量和 inode:

1
2
df -hT "$DST1"
df -i "$DST1"

大量小文件可能先耗尽 inode,即使 df -h 仍显示有可用容量。

Input/output error

立即查看内核和磁盘状态:

1
2
dmesg -T | tail -n 100
sudo smartctl -a /dev/sdX

/dev/sdX 替换为实际设备。出现持续读错误时,应先保护仍可读取的数据,不要反复执行全盘校验增加故障盘压力。

file has vanished

状态码 24 通常表示扫描后、复制前,源文件被移动或删除。

缓存、日志和下载目录中偶尔出现这种情况并不罕见。对需要一致性的业务目录,应暂停写入或从快照复制,然后重新运行并确认不再出现消失文件。

所有文件都被再次传输

常见原因包括:

  • 目标文件系统不能保留源端时间精度;
  • 应用在复制后又改写了文件;
  • 源和目标时钟异常;
  • 使用了会改变文件属性的挂载方式;
  • 文件经过中间程序重新编码或重新打包。

先用 --dry-run --itemize-changes 观察究竟是内容、大小、时间还是权限触发更新。

最终验收清单

完成两个大目录迁移后,至少保留以下证据:

  • 两条正式 rsync 命令均返回 0
  • 两份日志中没有未处理的 I/O、权限或空间错误;
  • 两组普通 dry-run 都没有意外输出;
  • 如需镜像,带 --delete 的 dry-run 没有意外删除项;
  • 文件数量和逻辑字节数相符;
  • 重要数据完成 rsync -nc 或独立 SHA-256 校验;
  • 关键文件能够从目标端实际打开;
  • 权限、ACL、xattr 和硬链接按业务需求保留;
  • 源盘在验收完成前没有被格式化或下线;
  • 有条件时完成一次从目标端恢复文件的演练。

最实用的确认命令仍然是:

1
2
3
4
5
6
sudo rsync -aHAXnc \
  --numeric-ids \
  --delete \
  --itemize-changes \
  /源目录/ \
  /目标目录/

它没有输出时,表示在当前排除规则和参数范围内,rsync 没有发现内容、路径或相关属性差异。

对于数 TB 的数据,严格校验很慢是正常现象。不要因为复制命令显示 100% 就立刻删除源数据;复制完成、校验通过和可恢复验证是三个不同的阶段。

常见问题

中断后需要添加特殊的“续传”命令吗?

通常不需要。重新运行相同的 rsync 命令即可。--partial --partial-dir=.rsync-partial 会保留未完成数据,已经一致的文件会被跳过。

两个大目录可以并行复制吗?

技术上可以,但如果它们位于同一源盘或同一目标机械硬盘,并行任务往往会降低吞吐量。默认串行更容易记录、重试和验收。

--progress--info=progress2 有什么区别?

--progress 更偏向显示当前文件进度,--info=progress2 显示整个传输任务的总体进度。超大目录通常后者更便于观察。

为什么复制完成后两边 du -sh 不一样?

文件系统块大小、稀疏文件、压缩、硬链接、元数据和保留块都可能导致占用不同。优先看 rsync 比对、文件清单和内容校验,不要只看 du

rsync -nc 会修改目标文件吗?

不会。-n 是 dry-run,-c 只是要求根据文件内容校验和判断差异。命令会大量读取磁盘,但不会执行复制或删除。

校验时为什么仍然要写 --delete

因为只比较源中存在的文件,无法发现目标端多出来的路径。与 -n 同时使用时,--delete 只报告这些额外内容,不会真的删除。

什么时候可以删除源目录?

至少等到两组同步退出状态正常、日志无未解决错误、严格校验通过,并从目标端成功打开或恢复关键文件之后。重要数据还应保留另一份独立备份。