fsnotify改一次触发多次事件是inotify底层行为所致,实操需时间窗口去重、忽略chmod/attrib、统一路径解析、手动注册子目录或换用实验版递归监听。

用 fsnotify 监听文件变化时,为什么改一次触发多次事件?
linux 下 mv、vim 保存、ide 自动格式化等操作常触发多个 fsnotify.Event,比如先 WRITE 再 CHMOD,或临时文件 CREATE + 原文件 REMOVE。这不是 bug,是底层 inotify 的行为。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 对同一路径的事件做时间窗口去重(如 100ms 内只处理最后一次
WRITE或CHMOD) - 忽略
CHMOD和ATTRIB类事件,除非你明确需要同步权限位 - 用
filepath.EvalSymlinks统一路径,避免软链接导致的重复监听 - 注意
fsnotify不递归监听子目录,需手动遍历注册,或改用golang.org/x/exp/fsnotify(实验版支持递归)
小文件走 http PUT 还是用 net/rpc 推送?
HTTP PUT 看似简单,但每次都要建连接、发 header、等响应,对高频小文件(如日志切片)延迟高、开销大;net/rpc 长连接更轻量,但得自己管序列化和错误重试。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 单文件 10:优先用
net/rpc+gob编码,服务端用rpc.register暴露SyncFile方法 - 需跨语言或走公网:改用 HTTP/2 +
multipart/form-data,客户端用http.Client复用连接,服务端用req.MultipartReader() - 别直接传完整文件内容——加个
file_id和mtime字段,接收方先比对再决定是否下载
io.copy 同步大文件时卡住,怎么加超时和断点续传?
io.Copy 本身不支持超时,网络抖动或对方宕机时会无限阻塞;也没有校验和或偏移记录,断连后只能重传整个文件。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
io.CopyN分块传输(如每 64KB 一块),每块前写入offset和sha256(chunk) - 读写都套一层带超时的
net.Conn:用conn.SetDeadline,不是SetReadDeadline单独设 - 接收方在写入前先
os.Stat查目标文件长度,若存在且非零,用os.OpenFile(..., os.O_APPEND)接续 - 别依赖
os.File的Seek做随机写——某些 NFS 或容器卷不支持,老实用os.WriteAt
多副本间如何避免“雪崩式同步”和状态不一致?
一个节点修改,其他节点收到通知后立刻拉取,若网络延迟不同,可能 A 先同步完、B 还在传,这时 C 又改了 A,A 就成了脏数据源。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有写操作必须带单调递增版本号(如
atomic.AddInt64(&version, 1)),同步时只接受更高版本 - 用
sync.RWMutex保护本地文件写入,读(对外提供下载)和写(接收同步)不能并发 - 副本间不直连同步,统一走中心协调节点(哪怕只是个内存 map +
sync.Map),由它分发变更顺序 - 每台机器启动时主动上报自己最新文件的
mtime和size,协调节点据此判断是否需要反向推送
真正麻烦的是时钟不同步和硬链接场景——os.SameFile 在不同挂载点可能误判,这种时候得靠 inode + device ID 双校验,但 windows 上就得换路子。