用 rsync 做增量同步比自己写 Go 备份更可靠,因其内置断点续传、权限保留、软链接处理、删除远端多余文件等能力,而 Go 仅需负责调度、日志与告警。

用 rsync 做增量同步比自己写 Go 备份更可靠
Go 本身不提供跨机器文件同步的抽象层,硬用 os + net 模拟 rsync 行为极易出错:断点续传、权限保留、软链接处理、删除远端多余文件等都得重造轮子。生产环境直接调 rsync 是更稳的选择——Go 只需负责调度、日志、失败告警。
常见错误是试图用 os/exec.Command("scp", ...) 全量拷贝,结果每次备份都耗时翻倍、磁盘爆满。而 rsync --archive --delete 能自动跳过未改动文件,且支持 --partial 断点续传。
- 确保远程服务器已启用 SSH 密钥登录(不用密码),否则
rsync会卡在交互式输入 - 本地执行命令前先手动跑一次:
rsync -avz --delete /path/to/local/ user@host:/path/to/remote/,确认路径、权限、网络连通性 - Go 中调用时务必设
cmd.Stderr = &stderrBuf,否则错误被吞掉,cmd.Run()返回nil但实际失败
exec.Command 启动 rsync 时必须显式设置 env 和 dir
Go 默认继承当前进程环境变量,但某些系统(如 systemd service 或 cron)下 $PATH 极简,rsync 找不到;同时相对路径在子进程里容易错乱。
正确做法是固定 PATH 并指定工作目录:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
cmd := exec.Command("rsync", "-avz", "--delete", "/data/", "backup@192.168.1.100:/backups/site/")
cmd.Env = []string{"PATH=/usr/bin:/bin"}
cmd.Dir = "/tmp" // 避免 cwd 影响相对路径解析
- 不要依赖
os.Getenv("PATH"),不同运行上下文差异太大 -
cmd.Dir设成空目录(如/tmp)能避免因当前目录权限不足导致 rsync 创建临时文件失败 - 若需压缩传输,加
--compress,但 CPU 较弱的 VPS 上慎用,可能拖慢整体速度
备份失败时靠 stderr 内容判断具体原因,不是只看 err != nil
rsync 退出码为 23 表示部分文件失败(如权限不足),但仍会返回 nil 错误;退出码 24 是文件 vanished,也常被忽略。只检查 err 会导致“看似成功实则漏备”。
- 必须捕获
stderr:var stderr bytes.Buffer; cmd.Stderr = &stderr - 检查退出码:
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 - 典型需告警的
stderr片段:"Permission denied"、"No route to host"、"Connection refused"、"failed: Broken pipe" - 日志里记下完整
stderr.String(),别只存 “rsync failed” 这种无用信息
定时任务用 cron 而不是 Go 的 time.Ticker
长期运行的 Go 程序在崩溃、升级、OOM 时会中断备份,而 cron 是操作系统级保障。Go 脚本应设计为单次执行、快速退出。
-
crontab -e加一行:0 2 * * * /usr/local/bin/backup-site >/var/log/backup.log 2>&1(每天凌晨 2 点执行) - Go 脚本开头加
if len(os.Args) > 1 && os.Args[1] == "test" { ... },方便手动触发测试 - 避免在脚本里 sleep 等待网络恢复——cron 本身可配置失败重试(如用
anacron),Go 不该承担调度职责
真正难的是 SSH 连接稳定性、远程磁盘空间预警、备份后校验一致性(比如 rsync --checksum 开销大但必要),这些没法靠一个 Go 文件包圆。先让 rsync 跑稳,再一层层加监控。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










