Appearance
05|rsync 同步与备份
上一篇的 scp 每次都全量传——一个 10GB 的备份目录改了一个文件,scp 又得传 10GB。rsync 不一样:它对比源和目标两边,只传变化的部分。改了一个文件,就只传那一个。
这种增量传输让 rsync 成了运维里最高频的文件同步工具——发布、备份、同步日志都靠它。本篇讲 rsync 怎么用,重点讲几个最容易出事的坑(路径斜杠、--delete)。
一、适用边界
适合的场景
- 发布静态资源到 Web 目录
- 从多台服务器拉日志到分析机
- 配置文件定期备份
- 跨机房同步文件(断线续传)
- 让目标目录与源目录保持镜像
不适合的场景
- 数据库数据目录(MySQL/PostgreSQL):有事务状态,同步出来的可能是损坏的
- 需版本快照的长期归档:应加专门的备份软件(borg、restic)
- 合规加密/索引/演练:rsync 不做这些
- 实时同步:rsync 是周期性触发,非 inotify 驱动
rsync 是文件级别同步工具,不是完整备份系统。小规模配置/脚本备份用 rsync 很方便,数据库应用各自的备份工具。
二、基础用法
rsync 的命令和 scp 长得像,区别在参数和它增量比对的特性。最基础的本地到远端:
bash
# 本地到远端
rsync -av /etc/nginx/ backup@10.10.1.20:/backup/server-a/nginx/
# 远端拉回本地
rsync -av backup@10.10.1.20:/backup/server-a/nginx/ ./nginx-restore/第一次跑是全量(两边没有比对基础),之后只传变化的部分。
常用参数组合
bash
# 最常用
rsync -avhP /data/app/ backup@host:/backup/app/
# 远端指定端口
rsync -avhP -e 'ssh -p 2222' /data/app/ backup@host:/backup/app/| 参数 | 含义 |
|---|---|
-a | archive 模式:递归+保留权限/时间戳/软链接/属主 |
-v | 显示文件列表 |
-h | 人类可读单位 |
-P | --partial --progress:断点续传+进度 |
-z | 传输时压缩(文本类有效,已压缩的文件无效) |
-n | --dry-run:预演,不实际修改 |
--delete | 使目标端完全=源端,删除多余文件 |
--backup | 覆盖或删除前保留备份 |
--bwlimit= | 限制带宽 |
--link-dest | 基于快照的硬链接增量(类快照) |
三、路径斜杠陷阱
rsync 源路径末尾的斜杠,结果完全不同:
bash
# 带斜杠:复制目录内容
rsync -av /data/app/ backup@host:/backup/app/
# → /backup/app/<files> (不产生 app 层)
# 不带斜杠:复制目录本身
rsync -av /data/app backup@host:/backup/
# → /backup/app/<files> (多一层 app/)带斜杠=复制内容,不带斜杠=复制目录。 未确认时先用 --dry-run 预览:
bash
rsync -avhP --dry-run /data/app/ backup@host:/backup/app/四、预演与变化查看
bash
# 预演
rsync -avhP --dry-run /data/app/ backup@host:/backup/app/
# 带变化标记
rsync -avhP -n --itemize-changes /data/app/ backup@host:/backup/app/变化标记解读:
| 标记 | 含义 |
|---|---|
>f+++++++++ | 源端有,目标端无,将会传输 |
cd+++++++++ | 目标端无此目录,将创建 |
*deleting | 目标端有但源端无,将被删除(仅 --delete) |
.f...p.... | 内容相同,只改权限 |
看到 *deleting 时要慢下来确认路径。
五、排除规则
bash
# 命令行排除
rsync -avhP \
--exclude 'node_modules/' \
--exclude '*.log' \
/srv/app/ backup@host:/backup/app/
# 从文件读取排除规则
cat > rsync-exclude.txt <<'EOF'
node_modules/
tmp/
*.log
.git/
.cache/
EOF
rsync -avhP --exclude-from rsync-exclude.txt /srv/app/ backup@host:/backup/app/排除规则是相对源目录做匹配的。 源目录是 /srv/app/,规则 tmp/ 匹配 /srv/app/tmp/,不是系统 /tmp/。
六、--delete 镜像同步
--delete 会让目标端变成源端的精确镜像:目标端多出来的文件会被删掉:
bash
rsync -avhP --delete /srv/app/dist/ web@host:/var/www/app/这个能力适合静态资源发布(旧版本的哈希文件不需要保留)。**但不适合备份场景:**源端误删时备份也跟着删。
bash
# 第一步:N 先用 --dry-run 确认
rsync -avhP --delete --dry-run /srv/app/dist/ web@host:/var/www/app/--backup:删除前保留旧文件
bash
rsync -avhP \
--delete \
--backup \
--backup-dir="/backup/deleted/$(date +%F-%H%M%S)" \
/srv/app/dist/ web@host:/var/www/app/--backup 在覆盖或删除前,把目标端旧文件保留到 --backup-dir。即便误删了,也能从 deleted/ 找回。
七、权限与属主
-a 默认保留属性,但能否保留属主/属组取决于执行用户:
bash
# root 执行:保留原属主/属组
# backup 用户执行:属主变为 backup强制设置权限
Web 静态文件发布场景:
bash
rsync -avh \
--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r \
./dist/ web@host:/var/www/app/| 写法 | 含义 |
|---|---|
Du=rwx | 目录:属主读写执行 |
Dgo=rx | 同组/其他:读执行 |
Fu=rw | 文件:属主读写 |
Fgo=r | 文件:同组/其他只读 |
别把 HTML 和图片传过去都设成可执行。
八、带宽与超时
bash
# 限制带宽(约 20 MiB/s)
rsync -avhP --bwlimit=20m /backup/full.tar.gz backup@host:/backup/
# I/O 超时(60 秒无数据传输则退出)
rsync -avhP --timeout=60 /data/app/ backup@host:/backup/app/
# SSH 保活
rsync -avhP \
-e 'ssh -o ConnectTimeout=10 -o ServerAliveInterval=30 -o ServerAliveCountMax=3' \
/data/app/ backup@host:/backup/app/--bwlimit 不同版本单位有差异——首次使用用小文件实测速度。 --timeout 是 I/O 超时,不是总时长——只要在传数据就不会触发。
九、备份脚本
bash
#!/usr/bin/env bash
# 备份脚本模板
set -euo pipefail
src="/etc/nginx/"
backup_host="backup@10.10.1.20"
backup_root="/backup/$(hostname)/nginx"
log_file="/var/log/rsync-nginx-backup.log"
mkdir -p "$(dirname "$log_file")"
rsync -avhP \
--delete \
--backup \
--backup-dir="$backup_root/deleted/$(date +%F-%H%M%S)" \
"$src" \
"$backup_host:$backup_root/current/" \
>> "$log_file" 2>&1定时执行(cron + flock)
cron
# 每天凌晨 2:10
10 2 * * * /usr/local/sbin/backup-nginx.sh防止并发:
bash
#!/usr/bin/env bash
flock -n /var/run/backup-nginx.lock /usr/local/sbin/backup-nginx-inner.shcron 里的 PATH 极少,脚本中所有命令用绝对路径。
十、--link-dest 快照备份
只保留一份 current/ 不够——源端误删后,备份也跟着丢。用 --link-dest 保存按日期的快照目录:
bash
backup_date="$(date +%F)"
rsync -avh \
--link-dest="/backup/app/current" \
/srv/app/ \
"backup@host:/backup/app/snapshots/$backup_date/"工作原理:文件内容与 --link-dest 目录相同的,创建硬链接,不额外占用空间;变化的文件才真正传输。
每天一个快照目录,物理上只占一份数据+每日变化量。
bash
# 更新 current 软链接
ssh backup@host 'cd /backup/app && ln -sfn snapshots/$(date +%F) current'
# 清理 30 天前的快照(先确认范围!)
find /backup/app/snapshots -mindepth 1 -maxdepth 1 -type d -mtime +30 -print
# 确认无误后删除
find /backup/app/snapshots -mindepth 1 -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;备份清理必须先 -print 确认,再删除。
十一、恢复验证
只同步不验证恢复等于没有备份:
bash
# 恢复到临时目录
rsync -avh backup@host:/backup/app/current/ /tmp/app-restore/
# 对比变化
diff -ruN /etc/nginx/ /tmp/nginx-restore/
# 确认后写回
rsync -avh --dry-run /tmp/nginx-restore/ /etc/nginx/
rsync -avh /tmp/nginx-restore/ /etc/nginx/
nginx -t # 语法检查
systemctl reload nginx # 加载nginx -t 放在 reload 前面——配置有语法错时直接 reload 会使服务异常。
十二、故障排查
| 现象 | 排查方向 |
|---|---|
| 目标端空间爆 | df -h;du -sh /backup/app/*;清理策略配了吗? |
| 同步慢 | iftop / nload 看网络;iostat -x 1 看 IO;小文件过多→扫描慢 |
| 权限不对 | 备份时加 -AX 保留 ACL 和扩展属性 |
| cron 没跑 | grep CRON /var/log/cron;脚本有执行权限?SSH key 要口令? |
| ssh key 要口令 | 备份账号用无 passphrase 的专用密钥 |
| 目标主机名解析失败 | 脚本里写 IP,或写 /etc/hosts 里确保有记录 |