Go微服务配置需外部化、分环境、可热更新、有fallback:用viper加载多格式文件,按ENV自动叠加环境配置,结构体Unmarshal设默认值,WatchConfig实现热更新但需手动处理依赖对象重载。
Go 微服务的配置不能硬编码,也不能靠环境变量“硬塞”,核心原则是:外部化、分环境、可热更新、有 fallback。下面直奔实操。
viper 加载多格式配置文件(YAML/JSON/TOML)viper 是 Go 生态事实标准,支持自动合并多个来源的配置(文件、环境变量、远程 etcd 等),且能监听文件变化。
config.yaml,再被 config.dev.yaml 覆盖(按需叠加)APP_,例如 APP_HTTP_PORT 会覆盖 http.port
viper.AutomaticEnv() 才能启用环境变量映射viper.SetConfigName("config") 不含扩展名;viper.AddConfigPath("./configs") 必须在 ReadInConfig() 前调用viper.SetConfigName("config")
viper.AddConfigPath("./configs")
viper.SetConfigType("yaml")
viper.AutomaticEnv()
viper.SetEnvPrefix("APP")
err := viper.ReadInConfig()
if err != nil {
log.Fatal("failed to read config: ", err)
}
不要用 if-else 切换结构体字段,而是让 viper 自动加载对应环境文件,并用 viper.GetEnv("ENV") 或 os.Getenv("ENV") 控制加载路径。
./configs/config.yaml(通用) + ./configs/config.dev.yaml(开发覆盖)ENV=prod go run main.go 触发加载 config.prod.yaml
viper 默认不支持“自动加载环境专属文件”,需手动判断并 ReadConfig 第二次:env := os.Getenv("ENV")
if env != "" {
viper.SetConfigName("config." + env)
viper.AddConfigPath("./configs")
_ = viper.MergeInConfig() // 不 pa
nic,失败也继续
}
直接用 viper.GetString("http.host") 容易 panic 或返回空字符串,应封装成结构体 + Unmarshal,并设默认值。
viper 支持的 tag:mapstructure:"db_port"
Port: 8080),避免运行时 panicviper.Unmarshal(&cfg) 后,再做字段级校验(比如 cfg.DB.Port > 0)*string)来“判断是否设置”,这会让逻辑变复杂且易出错type Config struct {
HTTP struct {
Host string `mapstructure:"host" default:"localhost"`
Port int `mapstructure:"port" default:"8080"`
}
DB struct {
URL string `mapstructure:"url"`
}
}
var cfg Config
err := viper.Unmarshal(&cfg)
if err != nil {
log.Fatal("failed to unmarshal config: ", err)
}
微服务上线后不能重启才能生效配置,viper.WatchConfig() 可监听 YAML 文件变更,但要注意副作用。
ReadInConfig() 之后调用 WatchConfig(),否则无 effectConfig 结构体并原子替换(如用 sync.Once 或 channel 通知模块重载)ConfigMap + subPath 挂载单个文件,再配合 fsnotify 监听真正难的不是读配置,而是当 DB.URL 在运行中变了,你的 gorm 实例要不要重建、连接池怎么平滑切换、旧连接何时关闭——这些不在 viper 职责内,得你自己画清楚边界。