服务器数据同步怎么实现?rsync配置、定时同步与异常恢复

主备两台服务器,要把主库产生的数据文件、用户上传的图片、配置文件同步到备机。最朴素的做法是 scp -r 或打个 tar 包传过去,但每天全量传一遍,几百 GB 的数据要跑几个小时,带宽和时间都扛不住。更大的问题是:同步脚本放到 crontab 里就以为万事大吉,直到某天切换时才发现备机的数据停留在三个月前——因为脚本早就报错退出了,而没有人看日志。rsync 是解决这类问题的标准工具,它只传输变化的部分,而且能告诉你"到底同步成功了没有"。本文从基本用法讲到定时任务的可靠设计,再到中断后的异常恢复。

一、rsync 为什么快:增量传输的原理

rsync 的核心不是"复制文件",而是"复制差异"。它的工作流程是:

  1. 源端和目标端先各自把文件按固定块大小切分,计算校验和(弱校验 rolling checksum + 强校验 md5 两类)。
  2. 目标端把自己的校验和列表发给源端。
  3. 源端比对,只把目标端没有的块和必要的匹配指令发过去。
  4. 目标端用本地已有的数据块 + 收到的新块重组出完整文件。

所以改一个 1GB 文件的中间几个字节,rsync 只传变化的那部分,而不是整个 1GB。这就是它比 scp 快几个数量级的原因。代价是两端都要消耗 CPU 做校验计算——在 CPU 很弱或文件极多时,CPU 可能先于带宽成为瓶颈。

判断文件是否需要同步的默认依据是文件大小 + 修改时间(mtime)。只要大小和 mtime 都一致就跳过。这带来一个重要推论:如果时间戳被改了但内容没变,rsync 会重新传;如果内容变了但时间戳没改(某些程序的行为),rsync 会漏掉。后者要用 --checksum(按内容校验和判断,慢得多)来解决。

二、核心参数速查

参数作用使用要点
-a(--archive)归档模式等价于 -rlptgoD:递归 + 保留符号链接、权限、时间戳、属组、属主、设备文件。几乎每次都要加
-v / -vv详细输出-v 列出文件,-vv 显示跳过原因
-z传输时压缩省带宽但耗 CPU。内网高速链路建议不用;跨公网、文本类数据推荐开启
--delete删除目标端多余文件危险参数,让目标端与源端严格一致。必须先 --dry-run 验证
--exclude / --include排除/包含规则支持通配符,可多次指定或用 --exclude-from=文件
--dry-run(-n)模拟执行,不实际改动所有危险操作前的必做步骤
--bwlimit限速(KB/s)避免同步打满业务带宽,如 --bwlimit=10240 即约 10MB/s
--partial / -P保留中断的部分文件-P = --partial --progress,大文件续传必备
--progress显示单文件进度交互式看进度用,脚本里不推荐
--info=progress2显示整体进度脚本中比 --progress 更合适
-H保留硬链接有硬链接的数据(如备份快照)必加
-A / -X保留 ACL / 扩展属性需要 SELinux 上下文或 ACL 时加 -X
--numeric-ids按数字 UID/GID 而非用户名两端用户名不一致时防止属主错乱
--checksum(-c)按内容校验和判断差异默认按 size+mtime;内容变了但时间没变的场景才用,慢
--link-dest硬链接到基准目录做增量快照备份的核心参数
-e指定远程 shell如 -e 'ssh -p 2222 -i /root/.ssh/id_rsa'

三、源目录结尾的斜杠:最经典的坑

这条必须单独讲,因为它是 rsync 误操作导致数据丢失的第一名原因。

  • rsync -av /data/ /backup/:把 /data 目录里面的内容同步到 /backup 里,结果是 /backup/a.txt。
  • rsync -av /data /backup/:把 /data 目录本身同步过去,结果是 /backup/data/a.txt。

差别只有一个字符,结果完全不同。更危险的是配合 --delete:如果写成了 rsync -av --delete /data /backup/,目标端 /backup 里的其他文件会被判定为"多余"而删除,而 /backup/data 里多出来的那层目录又不在删除范围内。所以每次写命令前,先用 -n 干跑一遍,逐行看输出再执行。

# 危险动作前的标准流程:先干跑
rsync -avn --delete /data/ user@backup:/backup/data/
# 逐行确认输出里没有意料之外的 delete,再去掉 n 执行
rsync -av --delete /data/ user@backup:/backup/data/

四、同步实操:本地、远程 SSH、守护进程三种模式

4.1 本地同步

# 本地目录同步(同机备份、迁移到新盘)
rsync -avHAX --numeric-ids /data/ /backup/data/

# 排除缓存与临时文件
rsync -av --exclude='*.tmp' --exclude='cache/' --exclude='.git/' /data/ /backup/data/

# 用规则文件管理排除项(规则多时更好维护)
cat > /etc/rsync-exclude.txt <

4.2 远程同步(SSH 模式,最常用)

# 配置免密 SSH(一次配置,长期生效)
ssh-keygen -t rsa -b 4096 -C 'rsync-backup'
ssh-copy-id -i ~/.ssh/id_rsa.pub backup@192.168.20.10
# 验证
ssh backup@192.168.20.10 hostname

# 推送:本地 → 远程
rsync -avz --delete -e 'ssh -p 22' /data/ backup@192.168.20.10:/backup/data/

# 拉取:远程 → 本地
rsync -avz -e 'ssh -p 22' backup@192.168.20.10:/data/ /backup/data/

# 限速 + 整体进度 + 保留中断文件(生产脚本推荐组合)
rsync -avz --delete --partial --info=progress2 --bwlimit=20480 \
      --exclude-from=/etc/rsync-exclude.txt \
      -e 'ssh -o StrictHostKeyChecking=no' \
      /data/ backup@192.168.20.10:/backup/data/

# 只同步某些子目录(用 --include + --exclude 组合,注意顺序)
rsync -avz --include='images/' --include='images/***' --exclude='*' \
      /data/ backup@192.168.20.10:/backup/data/

SSH 模式的优点是无需额外服务端配置、走成熟加密通道、权限控制清晰。缺点是每次要跑 SSH 握手,文件极多时开销明显。

4.3 rsyncd 守护进程模式(多客户端、高并发场景)

# 服务端配置 /etc/rsyncd.conf
cat > /etc/rsyncd.conf < /etc/rsyncd.secrets
chmod 600 /etc/rsyncd.secrets

# 创建运行用户与目录
useradd -M -s /sbin/nologin rsync
mkdir -p /backup/data && chown -R rsync:rsync /backup/data

# 启动(端口 873)
rsync --daemon
# 或用 systemd
systemctl enable --now rsyncd

# 防火墙
firewall-cmd --permanent --add-port=873/tcp && firewall-cmd --reload
# 客户端用法(注意是双冒号 :: 模块名)
rsync -avz --delete /data/ backup@192.168.20.10::data/
# 用密码文件避免交互
echo '你的密码' > /etc/rsync.pass && chmod 600 /etc/rsync.pass
rsync -avz --delete --password-file=/etc/rsync.pass /data/ backup@192.168.20.10::data/

# 列出服务端可用模块
rsync --list-only 192.168.20.10::

守护进程模式适合"很多台机器往一台备份服务器推数据"的场景:配置集中、可以做 IP 白名单、性能比 SSH 略好。缺点是传输默认不加密(跨公网要改用 SSH 模式或走隧道)。

五、定时同步:让脚本可靠而不是"看起来在跑"

把 rsync 丢进 crontab 是最容易出错的环节。常见问题有三个:上次没跑完下次又启动了(并发冲突)、失败了没人知道(静默退出)、SSH 免密在 cron 环境下失效(环境变量差异)。下面是可靠的脚本模板。

cat > /usr/local/bin/rsync-backup.sh <<'EOF'
#!/bin/bash
set -uo pipefail

SRC="/data/"
DST="backup@192.168.20.10:/backup/data/"
LOG="/var/log/rsync-backup.log"
LOCK="/var/run/rsync-backup.lock"
EXCLUDE="/etc/rsync-exclude.txt"

# 1. 加锁,防止上一次还没跑完就启动下一次
exec 9>"$LOCK"
if ! flock -n 9; then
    echo "$(date '+%F %T') 上一次同步尚未结束,本次跳过" >> "$LOG"
    exit 0
fi

# 2. cron 环境下显式指定环境,避免 ssh 找不到密钥
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export HOME=/root

echo "$(date '+%F %T') ===== 同步开始 =====" >> "$LOG"

# 3. 执行同步,捕获退出码
rsync -az --delete --partial --numeric-ids \
      --exclude-from="$EXCLUDE" \
      --bwlimit=20480 \
      -e "ssh -i /root/.ssh/id_rsa -o StrictHostKeyChecking=no -o BatchMode=yes" \
      "$SRC" "$DST" >> "$LOG" 2>&1
RC=$?

# 4. 按退出码区分处理(rsync 的退出码有明确含义)
if [ $RC -eq 0 ]; then
    echo "$(date '+%F %T') 同步成功" >> "$LOG"
elif [ $RC -eq 24 ]; then
    echo "$(date '+%F %T') 警告:部分文件在传输中消失(源码变动),非致命" >> "$LOG"
else
    echo "$(date '+%F %T') 同步失败,退出码 $RC" >> "$LOG"
    # 这里接告警:邮件/企业微信/钉钉 webhook
    echo "rsync 失败,退出码 $RC" | mail -s "rsync 同步失败 $(hostname)" ops@example.com
fi

echo "$(date '+%F %T') ===== 同步结束 rc=$RC =====" >> "$LOG"
exit $RC
EOF

chmod +x /usr/local/bin/rsync-backup.sh

# 加入 crontab:每天凌晨 2 点
crontab -l > /tmp/cron.bak
echo '0 2 * * * /usr/local/bin/rsync-backup.sh' | crontab -
crontab -l
rsync 退出码含义处理方式
0成功无需处理
23部分文件传输错误(权限、磁盘满等)需排查,检查日志中被跳过的文件
24部分文件在传输过程中消失(源端正在变动)通常非致命,重试即可;频繁出现说明源端变动过于剧烈
10客户端报错(语法、参数问题)检查命令与配置文件
11无法打开/建立 socket检查网络、rsyncd 是否启动、防火墙
12rsyncd 认证失败检查 secrets 文件内容与 600 权限
30超时调整 timeout,或检查网络质量
35等待守护进程超时检查服务端负载与 max connections

六、异常恢复:中断、误删与校验

6.1 传输中断后续传

# 加 --partial 保留已传部分,下次继续(大文件必备)
rsync -avzP --partial /data/bigfile.tar.gz backup@192.168.20.10:/backup/
# 重新执行同一条命令即可续传,rsync 会复用 .xxx.partial 临时文件

# 如果上次没加 --partial,已中断的文件会遗留临时文件,先清理
find /backup -name '.*.??????' -type f -print

6.2 误用了 --delete 导致目标端被清空

rsync 的删除是不可逆的,没有回收站。唯一可靠的事前防护是:

  1. 永远先跑 -n(dry-run)并把输出存档。
  2. 重要目标端开启文件系统快照(LVM snapshot、ZFS/btrfs 快照、或云硬盘快照),同步前打快照。
  3. 用 --backup --backup-dir=/backup/deleted_$(date +%F) 让被删除的文件先移到指定目录,而不是直接删除。
# 安全版删除:被覆盖/删除的文件先转移到备份目录
rsync -avz --delete --backup --backup-dir=/backup/deleted_$(date +%F) \
      /data/ backup@192.168.20.10:/backup/data/

# 用 LVM 快照做同步前的保护(ext4/XFS 均可)
lvcreate -L 20G -s -n snap_backup /dev/vg_data/lv_backup
# ...执行同步...
# 确认无误后删除快照
lvremove -f /dev/vg_data/snap_backup

6.3 同步完成后如何验证真的同步了

# 方法一:再跑一次 dry-run,无输出即表示已一致
rsync -avn --delete /data/ backup@192.168.20.10:/backup/data/

# 方法二:用校验和模式做深度比对(慢,但能发现内容不一致)
rsync -avcn --delete /data/ backup@192.168.20.10:/backup/data/

# 方法三:对关键文件做哈希比对
cd /data && find . -type f -exec sha256sum {} \; | sort > /tmp/src.sha256
ssh backup@192.168.20.10 'cd /backup/data && find . -type f -exec sha256sum {} \; | sort' > /tmp/dst.sha256
diff /tmp/src.sha256 /tmp/dst.sha256 && echo "一致"

# 方法四:统计文件数与总大小做粗校验
find /data -type f | wc -l
du -sb /data

小结与避坑建议

rsync 的核心价值在于"只传差异",但要让它真正可靠,靠的不是命令本身,而是围绕它建立的工程习惯:dry-run 先行、加锁防并发、退出码要判断、结果要校验、失败要告警。

避坑清单:

  • 源目录结尾漏写或错写斜杠,配合 --delete 后可能删错目录。写完先 -n。
  • 不加 --dry-run 就上 --delete。这是 rsync 数据事故的头号原因。
  • 把 --delete 用在备份场景。备份的目的是保留历史,源文件被删时备份里也应该保留一份。备份用 --backup-dir 或增量快照,不要直接 --delete。
  • cron 里 SSH 免密失效。cron 的 HOME 与 PATH 与交互 shell 不同,脚本里显式 export HOME=/root 并用 -i 指定密钥路径。
  • 不加锁。数据量大时一次同步可能超过 24 小时,下一次 cron 又启动,两个 rsync 同时写同一目标,结果不可预测。
  • 不检查退出码。rsync 失败时通常不会"卡住",而是安静地退出,cron 也不会报错。脚本里必须捕获 $? 并告警。
  • 日志不轮转。-v 模式日志增长很快,会写满磁盘。给 rsync 日志配 logrotate。
  • 跨公网用 rsyncd 明文传输。守护进程模式默认不加密,公网场景改用 SSH 模式。
  • 忽略时区与时间偏差。rsync 依赖 mtime 判断,两端系统时间相差较大时会导致大量不必要的重传。用 NTP 同步时间。
  • 以为同步等于备份。rsync 会把源端的误删"忠实"同步到目标端,--delete 更是如此。真正的备份需要保留历史版本(用 --link-dest 做快照式备份)。

最后给一个快照式备份的思路:用 --link-dest 可以做出"每个备份目录看起来都是完整全量,实际只占用增量空间"的效果,这是很多备份方案的基础。

# 快照式增量备份:每天一个目录,未变化的文件硬链接到昨天
rsync -a --delete --link-dest=/backup/snapshots/2024-06-14 \
      /data/ /backup/snapshots/2024-06-15/

本文为存储与硬盘管理系列,rsync 参数请以实际版本 rsync --version 为准。