Viper 不自动 fallback 多格式或重读文件,必须手动控制加载顺序、显式调用 SetConfigType、并在 WatchConfig 回调中执行 ReadInConfig 和 Unmarshal,否则热更新后仍返回旧值。

直接结论:viper 本身不自动 fallback 多格式,也不自动重读文件;必须手动控制加载顺序、显式设格式、并在 WatchConfig 回调里重新调 ReadInConfig() 和 Unmarshal(),否则 reload 后 viper.Get() 仍返回旧值。
为什么 viper.ReadInConfig() 会报 unknown config type
根本原因是 viper 默认只识别标准后缀(.json、.yaml、.yml、.toml 等),遇到 .conf、.cfg 或无后缀文件时无法推断格式。
- 必须在
ReadInConfig()前调用viper.SetConfigType("yaml"),不能依赖后缀 - 若从字节流或
embed.FS加载,SetConfigFile()完全无效,得用viper.ReadConfig(bytes.NewReader(data)) -
os.Stat()检查文件是否存在再加载,避免Config File "xxx" Not Found掩盖真实错误
如何让程序同时支持 config.json 和 config.yaml 并按优先级加载
viper 不支持同名多后缀自动 fallback。它只认一个 SetConfigName() + AddConfigPath() 组合,最终只加载第一个匹配文件。
- 手动按顺序尝试:
os.ReadFile("config.json")→ 成功则viper.SetConfigType("json");失败则试config.yaml→ 成功则viper.SetConfigType("yaml") - 不要写
viper.AddConfigPath("./")后直接ReadInConfig(),这只会找默认名(如config)且不试探后缀 - 若用 embed.FS,路径是虚拟的,
AddConfigPath()无效,必须用assets.ReadFile()+ReadConfig()
viper.WatchConfig() 不生效或 reload 后值没变
WatchConfig 只监听变更事件并触发回调,**完全不读文件、不解析、不刷新内部缓存**。常见“日志打印已更新但 viper.GetInt("port") 还是旧值”就源于此。
立即学习“go语言免费学习笔记(深入)”;
- 回调函数第一行必须是
err := viper.ReadInConfig(),且要检查 err;失败就 return,别往下走 - 紧接着必须
viper.Unmarshal(&newCfg),否则结构体字段仍是零值 -
SetConfigType()要在每次ReadInConfig()前设置,尤其当首次加载 JSON、后续改 YAML 时,类型不一致会静默失败 - 容器环境需确认
/proc/sys/fs/inotify/max_user_watches足够大,否则监听静默失效(Docker 启动加--sysctl fs.inotify.max_user_watches=524288)
并发读配置时 panic 或读到中间态
热更新不是“改几个字段”,而是整个配置实例替换。直接赋值 globalCfg = &newCfg 或修改字段,会在 goroutine 并发读取时产生 data race。
- 用
sync/atomic.Value存储*Config指针,Store()写入新实例,Load().(*Config)读取快照 - 别用
sync.RWMutex包裹GetConfig()全程——读锁开销大,且易漏defer导致死锁 - reload 失败时,
atomic.Value不替换,业务代码始终拿到上次成功加载的配置,这是最安全兜底 - 注意:YAML 中嵌套字段如
database.url可直接viper.GetString("database.url"),但热更新后必须确保所有消费方都通过该接口或 atomic.Value 读取,而非缓存旧指针


















