增量同步必须校验元数据和分块哈希,仅依赖modtime和size易出错;应先快速比对二者,均未变则跳过,任一变化则对小文件做分块哈希校验。

增量同步必须校验元数据 + 分块哈希,不能只看 ModTime
只靠 os.FileInfo.ModTime() 和 Size() 判断文件是否变更,线上几乎必出错:编辑器保存延迟、NFS 时间漂移、日志轮转后大小不变但内容已变……都会漏同步。全量比对又太慢,尤其 GB 级日志或配置目录。
实操建议:
- 先快速比对
ModTime()和Size(),二者都未变则直接跳过 - 任一变化,则对小文件(sha256.Sum256
- 大文件用分块哈希:按 1MB 切块,每块独立算
sha256,最后对所有块哈希再做一次哈希(避免块顺序影响) - 哈希结果与上一次同步时存的
hash.digest文件比对,不一致才触发传输
缓存双写必须“先更新 DB,再删缓存”,且删失败要兜底
go-zero 的 cachedConn.Update 就是硬编码这个顺序,不是巧合——它把不一致窗口压缩到毫秒级,且能自动收敛;而“先删缓存再更新 DB”一旦被并发读抢占,旧值就会回填,再也无法自愈。
实操建议:
- 别在事务里调
redis.Del:事务回滚会导致缓存误删、DB 未改,形成不可恢复的“空缓存+有数据”状态 - 删缓存失败时,
cachedConn默认静默忽略,必须上层补救:发cache_delete消息到 Kafka/RocketMQ,由独立 job 消费重试 - 轻量场景可用 Redis 的 key 过期事件:写
cache:order:123_signal并设 TTL,监听__keyevent@0__:expired后触发真实删除 - 绝对不要用
time.Sleep(500 * time.Millisecond)做延时双删——goroutine 阻塞、进程重启即失效
微服务间同步不能用本地事务,得靠 Saga + 外发事件
SQL tx.Commit() 对其他服务完全无效。强行封装“伪分布式事务”,在网络分区时卡死,补偿没触发就永久不一致。
实操建议:
- 每个写操作拆成两步:本地 DB 提交成功 → 再发 Kafka 消息(推荐事务性 producer)或写 pglogrepl 日志
- Saga 状态必须落库,字段至少含
status、step、compensated_step和version(防并发覆盖) - 补偿接口必须幂等:
RefundBalance(ctx, orderID, amount)开头先查是否已退过 - 别用 Redis 或内存存 Saga 进度——节点宕机就丢,没法重放
配置热更新时并发写 backup 文件会丢数据
多个 goroutine 同时调 os.WriteFile("config.bak.json", data, 0644),不是偶尔出错,是必然丢内容或写坏 JSON —— 这不是 Go 的问题,是没加锁。
实操建议:
- 所有配置写入路径(包括初始化和热更新回调)必须用同一把
sync.Mutex保护 - 统一用
os.WriteFile(不是已弃用的ioutil.WriteFile),它原子性更好,且保留文件权限 - 写前必须
os.MkdirAll确保父目录存在,否则no such file or directory错误会静默吞掉整个备份逻辑 - 写完立刻
os.Chmod("config.bak.json", 0600),防止敏感配置被其他用户读取
复杂点不在代码怎么写,而在所有环节都得考虑“失败后能否自愈”:哈希校验失败要不要告警,缓存删不掉会不会导致资损,Saga 补偿卡住有没有人工介入入口——这些才是线上真正卡住人的地方。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











