Go 配置管理

Go 服务配置管理方案,涵盖环境变量、Viper 多源配置、Config 结构体模式与热重载实践。

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

Go 配置管理

一句话定义

Go 配置管理是将应用运行参数(端口、数据库连接、特性开关等)从代码中分离,通过环境变量、配置文件、远程存储等多层来源统一加载和管理的工程实践。

核心机制 / 工作原理

12-Factor App 原则:配置严格与代码分离,通过环境变量注入。不同环境(dev/staging/prod)的差异仅在配置,不在代码。

环境变量(基础方案)

port := os.Getenv("APP_PORT")
if port == "" {
    port = "8080" // 合理默认值
}
dbURL := os.Getenv("DATABASE_URL")

简单直接,但缺乏类型转换、校验和层级结构。

Config 结构体模式

type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    Redis    RedisConfig    `mapstructure:"redis"`
}

type ServerConfig struct {
    Port         int           `mapstructure:"port"`
    ReadTimeout  time.Duration `mapstructure:"read_timeout"`
    WriteTimeout time.Duration `mapstructure:"write_timeout"`
}

// 加载后直接使用 cfg.Server.Port,类型安全

Viper 库(多源配置)

v := viper.New()
v.SetConfigName("config")        // 配置文件名(不含扩展名)
v.SetConfigType("yaml")          // 文件格式
v.AddConfigPath("./configs")     // 搜索路径
v.AddConfigPath("/etc/myapp")    // 多个搜索路径
v.AutomaticEnv()                 // 自动绑定环境变量
v.SetEnvPrefix("APP")           // 环境变量前缀:APP_SERVER_PORT
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

// 读取配置文件
if err := v.ReadInConfig(); err != nil {
    log.Printf("using defaults: %v", err)
}

// 解析到结构体
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
    log.Fatal(err)
}

配置优先级(从低到高):默认值 < 配置文件 < 环境变量 < 命令行参数 < 远程配置中心。

热重载

v.OnConfigChange(func(e fsnotify.Event) {
    var newCfg Config
    v.Unmarshal(&newCfg)
    // 通知各组件更新配置
    cfgChan <- newCfg
})
v.WatchConfig()

最小例子 / 最小场景

// configs/config.yaml
// server:
//   port: 8080
//   read_timeout: 5s
// database:
//   host: localhost
//   port: 5432
//   name: myapp

package main

import (
    "fmt"
    "github.com/spf13/viper"
)

type Config struct {
    Server struct {
        Port int `mapstructure:"port"`
    } `mapstructure:"server"`
}

func LoadConfig() (*Config, error) {
    v := viper.New()
    v.SetConfigFile("configs/config.yaml")
    v.AutomaticEnv()
    v.SetDefault("server.port", 8080)

    if err := v.ReadInConfig(); err != nil {
        return nil, err
    }

    var cfg Config
    return &cfg, v.Unmarshal(&cfg)
}

为什么重要

  • 环境一致性:同一份镜像/二进制通过不同配置部署到不同环境,消除”在我机器上能跑”问题。
  • 安全性:密钥和敏感配置通过环境变量注入,不提交到代码仓库。
  • 运维灵活性:特性开关、限流阈值等参数可在不重新部署的情况下调整。
  • 可测试性:配置可注入意味着测试时可使用内存数据库、Mock 服务等替代。

边界与易混淆点

  • Viper 不是唯一选择:小项目直接 os.Getenv + Config 结构体足够。Viper 的复杂度只有在多格式、远程配置、热重载等场景才值得。
  • mapstructure tag:Viper 使用 mapstructure 而非 json tag 做结构体映射,两者不要混用。
  • 环境变量命名:推荐 APP_ 前缀 + 下划线分隔(APP_DATABASE_HOST),避免与系统变量冲突。
  • 密钥管理:配置文件中的敏感值应使用占位符,实际值通过 Vault、AWS Secrets Manager 等注入。不要把生产密钥写进 config.yaml
  • 配置校验:加载后应立即校验(必填字段、端口范围、URL 格式),而非等到运行时才报错。可用 go-playground/validator 做结构体校验。
  • 时间解析:Viper 的 GetDuration() 对 YAML 中的 "5s" 字符串能正确解析,但 mapstructure 解析 time.Duration 需要配合 v.SetTypeByDefaultValue(true) 或自定义解析 Hook。
创建于 2026/6/25 更新于 2026/6/25