增量备份核心是用modtime().unixnano()和size()组合判断文件变更,记录于backup_manifest.json;写入需临时文件+rename保证原子性,校验用io.copy边读边算hash。

用 os.Stat 和 os.ReadDir 判断文件是否变更
增量备份的核心是识别「哪些文件变了」,不是靠文件名,而是靠修改时间 + 大小组合判断最稳妥。单独依赖 ModTime() 在某些文件系统(如 FAT32、NFS)或容器挂载场景下可能不准;只看大小又会漏掉内容不变但属性改了的情况。
实际做法是:记录上一次备份时每个文件的 ModTime().UnixNano() 和 Size(),本次扫描时对比这两个值都一致才跳过。
-
os.ReadDir比filepath.WalkDir更轻量,适合单层或可控深度的源目录,且不自动跟随 symlink,避免意外遍历 - 别用
os.Lstat代替os.Stat—— 增量逻辑关心的是最终内容是否变,不是链接本身 - 注意时区无关性:
UnixNano()返回 UTC 时间戳,跨机器比对安全;但若源和备份机时钟偏差 >1s,建议加 ±1s 容差(尤其 windows 上 FAT32 的 2s 时间精度)
用 io.Copy 配合 os.OpenFile 写入带校验的备份文件
写文件不是简单 os.Create 然后 io.Copy 就完事。增量备份要求原子性:要么全写成功,要么完全不留下残缺文件。否则下次增量扫描可能误判为「已存在但损坏」。
正确流程是:先写到临时文件(如 xxx.tmp),写完后 os.Chmod 设权限,再 os.Rename 覆盖目标路径。Windows 下 Rename 是原子的,linux 下也是(只要同文件系统)。
立即学习“go语言免费学习笔记(深入)”;
- 用
os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644),别漏O_TRUNC,否则追加写会污染旧内容 - 写完立即调
file.Sync()—— 尤其在 ext4 或 XFS 上,默认 write 不保证落盘,断电可能导致校验通过但内容损坏 - 如果需要校验,边
io.Copy边用hash.Hash(如sha256.New())计算,最后把 hash 存进元数据文件,而不是读一遍再算
用 JSON 文件存档 backup_manifest.json 记录状态
没有 manifest 的增量备份等于没做。不能靠目录结构推断,也不能靠时间戳猜上次备份点 —— 用户可能手动删过备份、挪过位置、甚至换过机器。
manifest 必须包含:源路径绝对路径(SourceRoot)、本次备份时间(timestamp)、每个已备份文件的相对路径 + ModTimeNano + Size + 可选 SHA256。文件名固定为 backup_manifest.json,放在备份根目录下,方便查找。
- 写 manifest 也必须走临时文件 +
Rename流程,否则备份中途崩溃会导致 manifest 和实际文件不一致 - 读 manifest 时用
json.Decoder而非json.Unmarshal,前者能处理超大文件(几十万文件时 manifest 可能 >10MB)且内存友好 - 不要把绝对路径硬编码进 manifest —— 如果备份目录被整体移动,路径失效。应存相对路径,并在加载时拼接当前 backup root
处理符号链接、权限、空目录这些容易被忽略的边界
默认 os.ReadDir 会返回 symlink 项,但不会自动解析。增量备份要不要跟进 symlink?取决于用途:若备份目标是还原整个开发环境,需解析;若只是归档代码,保留 symlink 原样更安全。
权限位(Mode())在 Linux/macos 上可完整保存,但 Windows NTFS 不支持 POSIX 权限,Chmod 会静默失败 —— 所以写权限前先 runtime.goOS == "windows" 分支判断。
- 空目录不会出现在
os.ReadDir结果里,但备份后要还原结构,所以 manifest 中需显式记录Type: "dir"的条目,哪怕Size: 0 - 遇到
syscall.EACCES或syscall.ENOENT错误时,跳过并记日志,别 panic —— 用户可能放了临时文件、锁住的数据库文件等 - macOS 的资源派生文件(
._xxx)和 .DS_Store 建议默认过滤,除非明确需要保留桌面布局
真正麻烦的从来不是「怎么拷贝」,而是「怎么定义『一样』和『不一样』」—— 时间精度、时区、文件系统语义、用户权限、跨平台行为差异,每一处都得在 manifest 里留证据,不然半年后你根本不知道那次备份到底信不信得过。