viper.WatchConfig()回调必须手动重载配置,否则viper.Get()始终返回旧值;需在回调中依次调用ReadInConfig()、Unmarshal(&newConf),并用atomic.Value原子替换配置指针,同时确保字段导出、inotify句柄充足。

viper.WatchConfig() 回调里必须手动重载配置
改了 config.yaml 文件,日志显示“配置已更新”,但 viper.GetInt("port") 还是旧值——这不是 bug,是预期行为。viper.WatchConfig() 只注册文件监听并触发回调,**不读文件、不解析、不刷新内部缓存**。
- 回调第一行必须是
if err := viper.ReadInConfig(); err != nil { ... },否则后续所有viper.Get()都在读上一次加载的缓存 -
viper.SetConfigType("yaml")必须在viper.ReadInConfig()之前调用,否则类型不匹配会静默失败(尤其首次用 JSON、Watch 时切 YAML) - 紧接着必须调
viper.Unmarshal(&newConf),传地址(&newConf),不是值(newConf);否则结构体字段仍为零值
用 atomic.Value 替换配置指针,别直接赋值全局变量
写 globalConf = &newCfg 是并发不安全的:多个 goroutine 同时读,可能看到 Port 已更新但 DBAddr 还是旧的,尤其结构体含指针或 map 字段时,极易 panic。
- 声明全局变量:
var globalConf atomic.Value - 初始化存默认配置指针:
globalConf.Store(&defaultCfg) - 热更新成功后:
globalConf.Store(newCfg)(newCfg是新解析的*Config) - 业务代码固定写法:
conf := globalConf.Load().(*Config),类型断言不能省,建议加非空判断
配置结构体字段必须全导出且不可变
json 和 yaml 包只序列化首字母大写的导出字段。小写字段会被静默跳过,热更后永远是零值。
- 错误写法:
type Config struct { port int }→port永远为 0 - 正确写法:
Port int、DBAddr string、Timeout time.Duration - 避免嵌入
map[string]interface{}或[]string—— 浅拷贝后仍共享底层数组,旧 goroutine 可能读到被意外改写的数据 - 推荐封装只读访问:
func (c *Config) GetDBAddr() string { return c.DBAddr },强制每次从最新实例取值
容器环境 inotify 句柄耗尽导致 Watch 失效
现象是“改了文件没反应”,控制台无报错,fsnotify 静默挂掉。根本原因不是磁盘满,而是 Linux 的 /proc/sys/fs/inotify/max_user_watches 耗尽(Docker 默认常为 8192)。
立即学习“go语言免费学习笔记(深入)”;
- Docker 启动必须加:
--sysctl fs.inotify.max_user_watches=524288 - Kubernetes 中需在 Pod spec 的
securityContext.sysctls中显式设置 - 验证是否生效:
cat /proc/sys/fs/inotify/max_user_watches在容器内执行


















