直接用Go自研同步逻辑90%属过度设计,除非有强定制需求;Syncthing已解决fsnotify监听漏子目录、并发覆盖、断点续传、版本回补等真实集群痛点,且生产环境只需调整rescanIntervalS、ignoreDelete、keepVersions三项配置即可保障一致性。

直接用 Go 自研同步逻辑,90% 的场景属于过度设计——除非你有强定制需求(比如合规要求禁止外连、必须走私有协议),否则优先选 Syncthing。
为什么不用自己写 fsnotify + HTTP 同步
看似简单:监听 /media 变更 → 计算 SHA-256 → 推给其他节点。但真实集群里会立刻撞上这些坑:
-
fsnotify不递归监听子目录,filepath.Walk时漏加某个子路径,新目录下的文件就永远不触发 - 并发上传同名文件(如
avatar.jpg)时,A 节点刚计算完哈希,B 节点已覆盖原文件 → 两边哈希不一致,但同步逻辑没做冲突检测,直接覆盖导致内容错乱 - 网络中断后重试没带断点续传,大文件反复全量传输,带宽打满还超时
- 节点临时下线期间的变更,上线后无法自动回补(因为没版本日志或状态机)
Syncthing 部署时必须改的三个配置项
Syncthing 默认开箱即用,但生产环境必须调以下三项,否则一致性不可控:
-
rescanIntervalS设为60(秒):默认 3600 秒太长,上传后要等一小时才同步;设太小(如 5)则频繁扫描拖慢 I/O -
ignoreDelete设为true:避免误删操作扩散到所有节点;删除应走业务层标记(如.deleted文件或元数据字段) -
keepVersions设为5:保留最近 5 个历史版本,用于人工回滚或排查 LWW 冲突(最后写入胜出不是万能的)
Go 程序如何安全读取 Syncthing 同步完成的文件
别在监听到 fsnotify.Create 就立刻读 —— Syncthing 是先写临时文件(如 photo.jpg.tmp),再原子 rename。你的程序得等 rename 完成才算真正就绪:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 监听
fsnotify.Rename事件,且Event.Name以.tmp结尾 → 忽略 - 监听到
Event.Op&fsnotify.Write != 0且!strings.HasSuffix(Event.Name, ".tmp")→ 才触发业务处理 - 加一层校验:
os.Stat确认文件 size > 0,再用sha256.Sum对比本地缓存的哈希(如果业务要求强一致性)
当必须自研时,绕不开的三个底层机制
如果真被逼到要自己写,别从 HTTP 上传开始,先搭好这三块地基:
- 内容寻址存储:所有文件入库前必算
sha256.Sum,路径按/data/{hash[0:2]}/{hash[2:4]}/{hash}存,避免单目录海量文件 - 追加式版本日志:每次新增文件,往本地
version.log写一行{timestamp}\t{hash}\t{size},用tail -n 1或 mmap 读最新 version,同步时拉取差值 - 节点间心跳与拓扑发现:不用 etcd,用 UDP 广播 +
net.InterfaceAddrs()自动识别内网 IP 段,每 5 秒发一次node:alive包,超时 3 次从同步列表剔除
最易被忽略的是版本日志的持久化时机——必须在文件写入磁盘后、fsync 完成才 append 到日志,否则崩溃重启后会出现“文件存在但日志没记”,导致其他节点永远收不到该文件。

















