侧边栏壁纸
  • 累计撰写 114 篇文章
  • 累计收到 2 条评论

服务器数据同步和异地备份怎么做?我用 rsync + SSH 搭了一套自动化方案,几个坑复盘

2026-5-18 / 0 评论 / 5 阅读
🤖AI摘要
本文介绍了使用 rsync + SSH 实现服务器数据同步和异地备份的自动化方案,分析了为何选择 rsync 而非 scp 或 rclone,并详细描述了基本用法及配置过程中可能遇到的五个常见问题:SSH 密钥认证、SSH 端口非标准情况、带宽限制和大文件传输、排除文件规则,以及删除目标端多余文件,并提供了解决方案。

之前写过一篇 MySQL 自动备份的,但备份文件只放在本机,总归不放心。后来有台服务器硬盘坏过一次,丢了两天的数据,从那以后我就开始折腾异地备份。

用了一圈方案,最后稳定跑在生产环境的就是 rsync + SSH。看起来简单,但实际配起来有不少细节,这里把踩过的坑整理一下。

为什么选 rsync 而不是 scp 或 rclone

scp 每次全量传输,文件大了慢得要命。rclone 功能多,但我只需要服务器到服务器的同步,装个 Go 程序有点重。rsync 是 Linux 自带的,增量传输只传差异部分,带宽占用小,断点续传也支持,对我来说刚好够用。

唯一的缺点是 rsync 本身不加密传输,所以必须走 SSH 通道,这也是下面要重点说的。

基本用法先跑通

最简单的同步命令:

rsync -avz -e ssh /data/backup/ user@remote:/data/backup/

几个参数解释:

  • -a:归档模式,保留权限、时间戳、符号链接等
  • -v:显示传输详情
  • -z:传输时压缩
  • -e ssh:指定用 SSH 做传输通道

注意 /data/backup/ 末尾的斜杠。有斜杠表示同步目录里的内容,没有斜杠会把整个目录同步过去变成 /data/backup/backup/。这个坑我踩过,第一次同步完发现多了一层目录,还以为数据丢了。

第一个坑:SSH 密钥认证不能用密码

手动执行 rsync 输入密码没问题,但放到 crontab 里定时跑,密码认证根本行不通。必须配 SSH 密钥对。

# 在备份源服务器上生成密钥
ssh-keygen -t ed25519 -f ~/.ssh/backup_key -N ""

# 把公钥传到目标服务器
ssh-copy-id -i ~/.ssh/backup_key.pub user@remote

然后在 rsync 命令里指定私钥:

rsync -avz -e "ssh -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

我第一次用的 RSA 4096 位密钥,后来换成了 ed25519,更短更安全,生成速度也快。

第二个坑:SSH 端口不是 22 的情况

生产环境为了安全,SSH 端口通常会改掉。rsync 指定端口的写法:

rsync -avz -e "ssh -p 2222 -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

这里有个容易忽略的点:-e 参数里的引号必须用双引号,用单引号在某些 shell 里会出问题。我之前在 CentOS 7 上用单引号跑了两个月没出事,换到 Ubuntu 22.04 就报错了,排查了半天。

第三个坑:带宽限制和大文件传输

白天同步把带宽打满,线上服务直接卡死。rsync 提供了限速参数:

rsync -avz --bwlimit=5000 -e "ssh -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

--bwlimit=5000 表示限制 5000KB/s,大约 5MB/s。根据自己的带宽调整,别问我怎么知道这个数值的——直接把 100M 带宽打满导致业务接口超时那次,被运维同事念叨了一个星期。

第四个坑:排除文件的正确写法

有些临时文件、日志碎片不需要同步。rsync 的排除规则:

rsync -avz --exclude='*.tmp' --exclude='.cache/' --exclude='node_modules/'   -e "ssh -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

如果排除规则多了,可以写到文件里:

# exclude.txt
*.tmp
.cache/
node_modules/
*.log

rsync -avz --exclude-from=exclude.txt   -e "ssh -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

我之前忘了排除 .cache/,同步了一堆浏览器缓存文件过去,目标服务器磁盘直接报警。

第五个坑:删除目标端多余文件

源端删了文件,目标端默认不会删。时间长了两边数据不一致。加上 --delete 参数:

rsync -avz --delete -e "ssh -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

但这个参数要慎用。我有一次源端目录挂载失败,rsync 认为所有文件都被删了,--delete 直接把目标端也清空了。双重灾难。

后来我加了防护措施:同步前先检查源目录是否存在且有文件:

#!/bin/bash
SOURCE="/data/backup/"
MIN_FILES=5

count=$(find "$SOURCE" -type f | wc -l)
if [ $count -lt $MIN_FILES ]; then
    echo "$(date): Source directory has only $count files, aborting sync" >> /var/log/backup.log
    exit 1
fi

rsync -avz --delete -e "ssh -i ~/.ssh/backup_key" "$SOURCE" user@remote:/data/backup/

这样源端目录异常时不会误删目标端数据。

完整的备份脚本

把上面的坑都处理了,最终的脚本长这样:

#!/bin/bash
# /opt/scripts/rsync_backup.sh

set -euo pipefail

SOURCE="/data/backup/"
REMOTE="user@backup-server"
REMOTE_PATH="/data/backup/"
SSH_KEY="$HOME/.ssh/backup_key"
SSH_PORT=2222
LOG="/var/log/rsync_backup.log"
MIN_FILES=5
BWLIMIT=5000

# 检查源目录
file_count=$(find "$SOURCE" -type f | wc -l)
if [ $file_count -lt $MIN_FILES ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: Source has only $file_count files, aborting" >> "$LOG"
    exit 1
fi

# 执行同步
rsync -avz --delete --bwlimit=$BWLIMIT     --exclude='*.tmp' --exclude='.cache/'     -e "ssh -p $SSH_PORT -i $SSH_KEY -o StrictHostKeyChecking=no"     "$SOURCE" "$REMOTE:$REMOTE_PATH" >> "$LOG" 2>&1

if [ $? -eq 0 ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') OK: Sync completed, $file_count files" >> "$LOG"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: rsync failed with exit code $?" >> "$LOG"
fi

StrictHostKeyChecking=no 是为了首次连接时不会因为交互确认而卡住。生产环境建议先把目标服务器的指纹加到 known_hosts 里,然后去掉这个参数。

配 crontab 定时执行

# 每天凌晨 3 点执行
0 3 * * * /opt/scripts/rsync_backup.sh

建议放在凌晨业务低峰期执行,配合 --bwlimit 限制带宽。

第六个坑:rsync 断点续传其实有条件

rsync 理论上支持断点续传,但默认行为是如果文件大小或修改时间变了,会重新传整个文件。对于大文件(比如数据库备份的 sql.gz),断网后重传很浪费。

加上 --partial--partial-dir 参数:

rsync -avz --partial --partial-dir=.rsync-partial   -e "ssh -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

--partial 会保留未传完的文件,下次从断点继续。--partial-dir 指定临时目录,不会污染目标目录。

校验备份是否完整

光同步还不够,得定期校验。用 --dry-run 看看有没有差异:

rsync -avzn --delete -e "ssh -i ~/.ssh/backup_key" /data/backup/ user@remote:/data/backup/

-n 是 dry-run 模式,只列出会传输的文件但不实际执行。如果没有输出,说明两边数据一致。

我每周跑一次 dry-run,把结果写到日志里。如果有大量文件要同步,说明上次同步可能出了问题。

写在最后

rsync 这个工具确实够用,但要注意的细节比想象中多。核心就是三件事:SSH 密钥别用密码、带宽要限制、删除操作要防护。把这三点做好,基本上可以稳定跑很久了。

如果你的数据量特别大(几百 GB 以上),可以考虑 rsync 的 --inplace 参数减少磁盘 IO,或者用 zstd 替代默认的 zlib 压缩。但大部分场景下,上面这套方案够用了。

评论一下?

OωO
取消