场景:一次“差点翻车”的日志归档
事情是这样的。去年我们有个业务线,每天凌晨需要将前端Nginx服务器的访问日志,增量同步到后端的集中存储服务器上,然后压缩归档。需求很简单:高效、增量、不丢数据。
我当时的初版命令是这样的(在源服务器上执行):
rsync -avz --delete /data/logs/ user@storage-server:/backup/logs/
看起来完美?-a 归档保留权限,-v 显示详情,-z 压缩传输,--delete 确保目标端删除源端已不存在的文件。但上线第一天,凌晨3点,告警电话就来了:“老K,日志目录没了!”
排坑第一弹:--delete 的“误杀”惨案
我赶紧登录检查。发现源端的 /data/logs/ 目录还在,但目标端的 /backup/logs/ 目录下的所有文件被清空了!问题出在哪?仔细一看命令末尾的斜杠。
关键知识点:rsync 对源路径末尾的斜杠 / 非常敏感。
-
rsync -avz /data/logs/ (带斜杠):表示复制目录内的内容,而不是目录本身。-
rsync -avz /data/logs (不带斜杠):表示复制目录本身。-
我的命令带了斜杠,相当于告诉 rsync:“把 /data/logs/ 下的所有文件同步过去,目标端 /backup/logs/ 里如果有多余的文件,就删掉。”
但问题在于,当天凌晨,源端 /data/logs/ 目录因为日志轮转(logrotate)短暂变成了空目录。rsync 一执行,发现源端是空的,于是“尽职尽责”地把目标端 /backup/logs/ 下的所有历史日志文件全删了!
解决方案:永远不要在 --delete 场景下,对源路径使用末尾斜杠,除非你100%确定源目录不会瞬间变空。 更防护的做法是同步目录本身:
rsync -avz --delete /data/logs user@storage-server:/backup/
这样,目标端会生成 /backup/logs/ 目录,即使源端 /data/logs 临时为空,目标端的 /backup/logs 目录本身依然存在,只是内容被清空,但目录结构保住了。如果连目录结构都不想被影响,可以配合 --backup --backup-dir 做版本化备份,但那是后话了。
排坑第二弹:大文件同步中断与断点续传
解决了误删问题,第二个坑又来了。我们的日志文件单个经常超过2GB,网络抖动时,rsync 同步到一半就中断了。重新执行时,它居然从头开始传!
原因:rsync 默认的增量传输是基于文件级别的“块校验”,但校验前需要先读取整个文件。对于超大文件,一旦中断,下次重跑时,它会重新读取源文件并重新计算校验块,导致大量重复的磁盘I/O和网络传输。
解决方案:加上 --partial 和 --progress 参数。
rsync -avz --partial --progress --delete /data/logs user@storage-server:/backup/
-
--partial:保留部分传输的文件。默认情况下,rsync 传输中断会删除未完成的临时文件。加上这个参数,它会保留 .xxxxx 的临时文件,下次执行时,rsync 会基于这个临时文件进行断点续传。-
-
但注意,--partial 只是保留文件,真正的“增量续传”需要 rsync 的算法支持。实际上,rsync 在传输大文件时,默认就会将文件切分成固定大小的块(默认700字节,可通过 --block-size 调整),然后只传输变化的块。加上 --partial 后,中断恢复时,它会读取已存在的临时文件,只传输缺失的块,这才是真正的断点续传。
排坑第三弹:带宽占用与限速
日志同步通常是在业务高峰期之后的凌晨进行,但万一遇到突发流量,rsync 默认会“抢带宽”,导致正常业务响应变慢。
解决方案:使用 --bwlimit 参数限速。
rsync -avz --partial --progress --bwlimit=5000 --delete /data/logs user@storage-server:/backup/
--bwlimit=5000 表示控制带宽为 5000 KB/s(约5MB/s)。这个值需要根据实际网络带宽和业务容忍度来调整。如果业务对网络延迟极其敏感,甚至可以考虑用 ionice 和 nice 降低 rsync 进程的磁盘I/O和CPU优先级。
终极方案:脚本化与监控
单条命令再完美,也扛不住人肉运维。我最终把它封装成了一个带日志、带锁、带健康检查的脚本。
#!/bin/bash
# rsync_log_backup.sh
LOCKFILE="/var/run/rsync_log_backup.lock"
LOGFILE="/var/log/rsync_backup.log"
SOURCE="/data/logs"
DEST="user@storage-server:/backup/"
BWLIMIT=5000
# 防止脚本并发执行
exec 200>$LOCKFILE
flock -n 200 || { echo "[$(date)] Another instance is running, exiting." >> $LOGFILE; exit 1; }
echo "[$(date)] Starting rsync backup..." >> $LOGFILE
rsync -avz --partial --progress --bwlimit=$BWLIMIT --delete $SOURCE $DEST >> $LOGFILE 2>&1
if [ $? -eq 0 ]; then
echo "[$(date)] Rsync completed successfully." >> $LOGFILE
# 这里可以触发后续的压缩、清理等动作
else
echo "[$(date)] Rsync failed with exit code $?." >> $LOGFILE
# 发送告警通知(邮件、钉钉等)
fi
flock -u 200
把这个脚本放进 crontab,每天凌晨执行:
0 3 * * * /usr/local/bin/rsync_log_backup.sh
总结
rsync 是个好工具,但用好它需要敬畏心。总结几个核心经验:
-
- 斜杠是魔鬼:
--delete 场景下,源路径尽量不加末尾斜杠,同步目录本身。 -
- 大文件要续传:
--partial 是断点续传的基石,配合 --progress 方便排查。 -
- 带宽要管控:
--bwlimit 是生产环境的必备参数,别让备份影响了业务。 -
- 脚本化保平安:加锁、日志、告警,让机器去干活,人只负责看戏。
-
希望这些踩坑经验能帮你少走弯路。记住,备份不是“做了就行”,而是“能恢复才行”。下次咱们聊聊 rsync 结合 inotify 做实时同步的坑,那又是另一个故事了。
👨💻 运维兵经验:根据实际生产环境,以上步骤建议先在测试环境验证,并做好备份。参数值需根据服务器设置调整,不要盲目照搬。