air不重启根本不是工具故障,而是未监听到修改文件:必须在go.mod根目录运行、root设为"."、watch显式包含子目录及非Go扩展名,并确保build.bin与build.cmd路径严格一致。

为什么改了代码 air 不重启
根本不是工具坏了,是它根本没“看见”你改的文件。air 默认只监听当前工作目录下的 .go 文件,不递归进 internal/、pkg/,也不跨模块扫描——如果你的 handler 在 internal/handler/user.go,却在 cmd/api 目录下运行 air,那修改就完全无效。
- 确认你在
go.mod所在根目录执行air,不是子命令目录 -
air.toml中root字段留空或设为.,别写死绝对路径 - 多 main 项目(如
cmd/api和cmd/worker)必须显式配置main_cmd = "go run cmd/api/main.go" - Windows 用户注意:所有路径统一用正斜杠
/,.\tmp\main会导致构建失败
build.bin 和 build.cmd 路径不一致导致 exec: "./tmp/main": file does not exist
这个错误不是编译失败,是 air 启动时找不到可执行文件。它严格依赖 build.bin 值和 build.cmd 输出路径完全一致——哪怕一个字符、一个斜杠方向不同都会失败。
-
build.cmd必须是能生成二进制的命令,例如"go build -o ./tmp/main .",不能写go run main.go -
build.bin必须与build.cmd的-o参数值完全相同,包括前缀(./tmp/main或tmp/main) - Linux/macOS 下路径区分大小写,
./Tmp/main和./tmp/main是两个路径 - 容器内运行时,确保
build.bin路径在容器文件系统中可写(比如挂载了tmp/目录)
viper.WatchConfig() 改了配置文件却没生效
viper.WatchConfig() 只注册监听器,不做任何读取或更新操作。它调完就返回,如果主线程随后退出,监听根本不会触发——更别说更新配置了。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 程序必须长期存活:启动
http.ListenAndServe()或加select{}(仅调试用) -
viper.AddConfigPath()和viper.SetConfigName()必须在viper.WatchConfig()之前调用 - 回调函数里第一行必须是
viper.ReadInConfig(),第二行是viper.Unmarshal(&newCfg),缺一不可 - 配置结构体必须全字段导出,且不能含
sync.Mutex等不可复制字段,否则atomic.Value.Store()会 panic
容器里热重载静默失效:No space left on device 不是磁盘满
这是 Linux 内核 inotify 句柄耗尽的典型表现。Docker 默认限制极低(常为 8192),而微服务项目文件多、监听深,很容易打满。
- Docker 启动时必须加
--sysctl fs.inotify.max_user_watches=524288,宿主机调高无效,容器内也要一致 - K8s 中 ConfigMap 挂载后不更新?先检查挂载点权限:
ls -l /etc/config/app.yaml,确保 Go 进程有读权限 - 稳妥做法:启用轮询模式,启动前设环境变量
VIPER_CONFIG_WATCH_POLL=true(v1.12+ 支持) - Realize 或 CompileDaemon 在容器里也需同样 sysctl 配置,否则
fsnotify回调根本不会触发
复杂点在于路径、权限、生命周期三者必须同时对齐——少一个,热重载就变成“看起来在跑,实际没反应”。多数问题不是配置漏项,而是某处路径用了反斜杠、某次 go run 残留进程占着端口、或者回调里忘了 viper.Unmarshal()。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










