fsnotify不能监听单个文件,因编辑器原子写入会销毁原inode;必须监听父目录,用filepath.base()过滤事件,并手动递归添加子目录、过滤临时文件、同时消费events和errors通道。

fsnotify 是唯一能实现实时、低开销、跨平台目录监控的方案,但直接监听单个文件或忽略路径存在性检查,90% 的线上故障都源于此。
为什么不能用 w.Add("config.json") 监听单个文件
编辑器(VS Code、vim 等)保存时普遍采用 atomic write:先写 config.json.12345,再 os.Rename() 覆盖原文件。原路径瞬间失效,fsnotify 不会触发新文件的 Create 事件——因为你根本没监听那个目录。
- 必须监听父目录,例如
w.Add("configs/"),而非具体文件路径 -
event.Name是相对路径(如"configs/config.json"),用filepath.Base(event.Name)提取文件名做判断 - 临时文件(
*.tmp、*.swp、~结尾)需主动过滤,否则会误触发
如何递归监听整个目录树
fsnotify 默认不递归,子目录变动不会上报。必须手动遍历并逐个 Add,且需处理新增子目录的动态监听。
- 启动时用
filepath.WalkDir遍历所有现有子目录,对每个os.DirEntry调用w.Add(entry.Name()) - 监听到
fsnotify.Create且event.IsDir()为 true 时,立即w.Add(event.Name)新目录 - 跳过符号链接(
entry.Type() & os.ModeSymlink != 0),除非你明确要监控链接自身 - 若目录权限不足或不存在,
w.Add返回 error,必须检查并记录,否则静默失败
watcher.Events 和 watcher.Errors 必须同时消费
任一 channel 阻塞都会导致整个 watcher 卡死:Linux inotify fd 耗尽、goroutine 泄漏、后续事件全丢。
- 绝不能只读
Events而忽略Errors;也不能只读Errors而丢弃Events - 推荐用
select+default非阻塞轮询,或起两个独立 goroutine 分别处理二者 -
w.Close()后,仍可能有 1 个残留 event 或 error 待读取,需在关闭前清空 channel(最多各读 1 次) - 不要用
done := make(chan bool)配合停止循环——它无法响应错误或优雅退出
容器或 NFS 环境下 fsnotify 失效怎么办
inotify 依赖宿主机内核的 inotify 实例,而 Docker 默认不共享该资源;NFS 客户端不转发事件,服务端也收不到变更通知。
- Docker 中加
--cap-add=SYS_INOTIFY_INIT仅部分镜像支持,多数情况下无效 - Kubernetes ConfigMap/Secret 卷是 tmpfs,
fsnotify可用,但更新只触发Chmod(mtime 改变),不是Write - 真正可靠的做法是 fallback 到轮询:
time.Ticker每 1–5 秒调用os.Stat比较ModTime()和Size(),尤其适用于挂载卷场景 - 轮询逻辑必须和
fsnotify共存:优先走事件,失败后自动降级,重启时恢复监听
fsnotify 就只是个摆设。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











