用 rsync + 硬链接做增量备份:低成本的版本化备份方案
很多中小规模的服务器备份,最后都卡在同一个问题上:全量备份太占空间,每天一份放一周就要七倍容量;纯增量备份恢复时又要逐份回放,链条一断全盘皆输。rsync 的 --link-dest 方案是个经典折中:每次备份在文件系统层面做一份「完整目录树」,没变化的文件用硬链接与上一份共享同一份数据,实际新增占用只有当天变更量。本文给出可直接落地的脚本与注意事项。
适用环境:Rocky Linux 9 / RHEL 9,文件系统为 XFS 或 ext4(都支持硬链接;注意目标目录必须与源在同一文件系统内才能硬链接,跨盘备份时 link-dest 的基准目录要和新备份目录在同一文件系统上)。Ubuntu 22.04 仅包管理命令不同,脚本本身通用。
方案原理
硬链接是同一 inode 的多个目录项,多个文件名指向同一份数据块,删除其中一个名字不影响数据本体。rsync 加上 --link-dest=DIR 后,同步时会逐个文件与 DIR 中的对应文件比对:大小和 mtime 都没变的,直接在目标目录里建硬链接指向 DIR 里的那个 inode,不复制数据;变了的才真正传输并写入新 inode。于是每次备份目录打开都像一份完整全量,而磁盘开销约等于「首份全量 + 历次增量」。
关键推论有两条:第一,任何一份备份目录都可以独立拿来恢复,不存在增量链依赖;第二,对旧备份里的文件做「原地修改」会污染所有共享该 inode 的备份,所以备份目录必须只读对待,要改只能先断链接(复制再改)。
前置条件
- rsync 已安装(Rocky Linux 9:
sudo dnf install -y rsync;Ubuntu:sudo apt install -y rsync); - 备份目标空间至少能容纳一份全量加预期增量,建议单独分区或单独磁盘挂载;
- 远程备份时,源机到目标机已配好 SSH 密钥免密登录;
- 明确要备份的目录清单与排除规则(缓存、临时文件、日志通常不进备份)。
编写备份脚本
脚本的骨架是:以日期命名建新目录 → 以最近一份成功备份为 link-dest 基准 → rsync 同步 → 更新 last 软链接 → 清理过期目录。再加上文件锁防并发、失败不更新基准这两个细节,才算生产可用。
#!/usr/bin/env bash
# rsync + link-dest 版本化备份脚本(本机备份示例)
set -u
SRC="/srv/appdata/" # 源目录,末尾斜杠表示备份目录内容
DEST_ROOT="/backup/node-01" # 备份根目录
KEEP_DAYS=14 # 保留天数
DATE="$(date +%F_%H%M)"
DEST="$DEST_ROOT/$DATE"
LAST="$DEST_ROOT/last"
LOG="/var/log/backup-rsync.log"
exec 9>/var/lock/rsync-backup.lock
flock -n 9 || { echo "$(date '+%F %T') another backup is running, exit" >>"$LOG"; exit 1; }
mkdir -p "$DEST_ROOT"
LINK_OPT=()
if [ -d "$LAST" ]; then
LINK_OPT=(--link-dest="$LAST")
fi
rsync -aHAX --numeric-ids --delete \
--exclude='/cache/' \
--exclude='*.tmp' \
"${LINK_OPT[@]}" \
"$SRC" "$DEST/" >>"$LOG" 2>&1
RC=$?
if [ "$RC" -eq 0 ] || [ "$RC" -eq 24 ]; then
# 24 = 部分源文件在传输中消失,通常可接受
ln -sfn "$DEST" "$LAST"
find "$DEST_ROOT" -maxdepth 1 -type d -name '20??-??-??_*' \
-mtime +"$KEEP_DAYS" -exec rm -rf {} +
echo "$(date '+%F %T') backup ok: $DEST" >>"$LOG"
else
echo "$(date '+%F %T') backup FAILED rc=$RC, partial dir kept: $DEST" >>"$LOG"
exit "$RC"
fi
有几个刻意的选择要说明。失败时不更新 last 软链接,这样下一次备份仍以上一份成功备份为基准做硬链接,失败留下的半成品目录会被保留策略自然淘汰,不会让某个文件「以为自己已备份」。退出码 24 是 rsync 的常见良性码(源文件在扫描后被删除),按成功处理但日志里能查到明细。保留策略这里简单地按目录 mtime 删 KEEP_DAYS 天前的目录;要求更精细的可用「保留最近 7 份 + 每周 1 份」这类分层策略,改 find 的筛选条件即可。
关键参数逐项说明
-a:归档模式,等价于 -rlptgoD,递归并保留权限、属主、时间戳等常规属性,是备份场景的底线配置。-H:保留源端的硬链接关系。源目录内部本身有硬链接时,不加 -H 会把每个链接名各复制一份,既费空间也改变语义。-A/-X:分别保留 ACL 与扩展属性(包含 SELinux 上下文 security.selinux)。漏掉 -X 在 RHEL 系上最典型的后果是恢复后文件 SELinux 标签不对,服务莫名被拒。--numeric-ids:按 uid/gid 数字映射,避免源机与目标机同名账号 ID 不一致导致的属主错乱,跨机备份强烈建议加。--delete:删除目标里源端已不存在的文件,让备份目录与源保持镜像。风险点在于:源目录被误挂载为空、或源路径写错时,--delete 会把「空」也镜像过去。link-dest 方案下历史版本还在旧目录里,影响可控,但对 last 基准目录仍要先做源目录非空检查再跑正式任务。--exclude:规则以源根目录为参照,/cache/开头的斜杠表示锚定根目录。排除规则写错是最常见的「备份里啥都有、就是没我要的」来源,上线前务必用--dry-run -v过一遍清单。
远程 SSH 传输
把备份推到另一台主机时,目标改为 user@host:path 形式,并显式指定 ssh 参数以便用密钥与自定义端口;大文件、窄带宽时加 --bwlimit 限速避免打满链路:
rsync -aHAX --numeric-ids --delete \
-e "ssh -i /root/.ssh/backup_ed25519 -p 22" \
--link-dest=/backup/node-01/last \
--bwlimit=20000 \
/srv/appdata/ backup@192.0.2.50:/backup/node-01/2026-11-08_0200/
注意远程模式下 link-dest 必须是目标机上的绝对路径。另外把脚本放进 cron 时建议顺手加随机延迟(sleep $((RANDOM % 600))),多台机器别在同一时刻齐发把备份机网卡打爆。
恢复:备份的真正验收方式
备份有没有用,只看恢复演练。恢复就是一次反方向的 rsync,或者小文件直接 cp。整目录恢复示例:
# 恢复到临时目录核对,再切换上线
sudo rsync -aHAX /backup/node-01/2026-11-07_0200/ /srv/appdata-restore/
ls -la /srv/appdata-restore/
sudo rsync -aHAX --dry-run /backup/node-01/2026-11-07_0200/ /srv/appdata/
演练要点:抽查关键文件的权限、属主、SELinux 上下文(ls -Z)与内容校验和;应用类数据要实际起一次服务读一遍,而不是「文件拷回去了」就算完。每季度至少做一次整目录级恢复计时,这个数字才是你能承诺的 RTO。
与快照、对象存储的取舍
LVM/ZFS/Btrfs 快照做「本机回滚」更快,秒级生成,但快照与源数据同盘同系统,救不了盘坏和误删卷这类故障。对象存储(如 S3 兼容存储)适合异地与长期归档,工具链一般换成 restic、rclone 等。rsync + 硬链接方案的定位是:一台独立备份机、保留两到四周版本、恢复简单粗暴。数据规模上到 TB 级且变更率很高时,硬链接的元数据开销和 find 清理都会变慢,应考虑专业备份工具。
验证
- 连续跑两次脚本,第二份备份目录的
du -sh远小于第一份;对某个未变更文件执行ls -li,两份备份中该文件的 inode 号相同,即硬链接生效; - 在源目录改一个文件再跑一次,新目录中该文件 inode 与旧目录不同、内容为新版本,旧目录内容不变;
- 按上文流程完成一次单文件恢复与一次整目录 dry-run,校验和一致;
- 日志中无 FAILED 记录,last 软链接指向最新成功备份。
常见问题
备份空间爆炸,硬链接没生效。先看基准目录传对没有:link-dest 路径不存在时 rsync 会静默退化为全量复制。其次是源文件 mtime 天天变(如某些应用每次都重写整个文件),比对必然判定「已变化」。还有一种隐蔽情况:目标文件系统不支持硬链接(部分网络文件系统),写入时才会暴露,部署前先用两个小文件实测一遍。
恢复后权限或属主不对。同步时漏了 -A/-X,或者恢复时用了普通用户跑(非 root 无法保留属主与 ACL)。备份和恢复两端都要以 root 执行,并核对参数一致。
误删被同步进最新备份。--delete 让最新一份与源严格一致,误删自然会传播;但历史日期目录还在,从上一日目录取回即可。这正是版本化的价值,也提醒你:保留策略别设得太短,误删往往隔几天才发现。
备份越跑越慢。文件数到百万级后,rsync 的全量文件列表比对本身就耗时。可把大目录拆成多个任务并行跑,或评估迁移到 restic 这类带索引的备份工具。
小结
rsync + 硬链接方案没有新概念,全是基本功:--link-dest 做增量、-aHAX 保属性、last 软链接管理基准、失败不推进基准、定期做恢复演练。把这几件事做扎实,就能用近乎零成本得到一份可独立恢复的版本化备份。等规模真的 outgrow 它时,再迁移到快照或专业备份系统也不迟。







