Viper - Go 配置管理神器
Viper 是 Go 最流行的配置管理库,支持多种配置格式、环境变量、命令行参数、远程配置中心等。掌握 Viper 是开发生产级 Go 应用的基础。
📌 核心概念
📄
多格式
JSON/YAML/TOML
.yaml
🔄
热重载
配置变更监听
WatchConfig
🌐
远程配置
etcd/Consul
Remote Config
⚙️
优先级
多来源合并
Priority
快速开始
📝 基础使用
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
// 设置配置文件名
viper.SetConfigName("config")
// 设置文件类型
viper.SetConfigType("yaml")
// 设置搜索路径
viper.AddConfigPath("./configs")
viper.AddConfigPath("/etc/myapp")
// 读取配置
if err := viper.ReadInConfig(); err != nil {
panic(fmt.Sprintf("Config error: %v", err))
}
// 读取配置值
host := viper.GetString("server.host")
port := viper.GetInt("server.port")
debug := viper.GetBool("app.debug")
fmt.Printf("Server: %s:%d, Debug: %v\n", host, port, debug)
}
💡 Viper 要点
- 自动搜索: 在多个路径查找配置文件
- 类型安全: GetString/GetInt/GetBool 等类型方法
- 嵌套访问: 使用点号访问嵌套配置
- 默认值: SetDefault 设置默认值
配置文件
📝 YAML 配置示例
# config.yaml
server:
host: "0.0.0.0"
port: 8080
timeout: "30s"
database:
driver: "mysql"
dsn: "user:pass@tcp(localhost:3306)/db"
max_idle: 10
max_open: 100
redis:
addr: "localhost:6379"
password: ""
db: 0
app:
name: "MyApp"
version: "1.0.0"
debug: true
log_level: "info"
features:
- "feature1"
- "feature2"
- "feature3"
📝 读取配置到结构体
package main
import (
"fmt"
"github.com/spf13/viper"
)
// 配置结构体
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
App AppConfig `mapstructure:"app"`
}
type ServerConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Timeout string `mapstructure:"timeout"`
}
type DatabaseConfig struct {
Driver string `mapstructure:"driver"`
DSN string `mapstructure:"dsn"`
MaxIdle int `mapstructure:"max_idle"`
MaxOpen int `mapstructure:"max_open"`
}
type RedisConfig struct {
Addr string `mapstructure:"addr"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
}
type AppConfig struct {
Name string `mapstructure:"name"`
Version string `mapstructure:"version"`
Debug bool `mapstructure:"debug"`
LogLevel string `mapstructure:"log_level"`
}
func main() {
viper.SetConfigFile("./configs/config.yaml")
viper.ReadInConfig()
// 方式 1: 手动读取
host := viper.GetString("server.host")
port := viper.GetInt("server.port")
// 方式 2: 解组到结构体
var config Config
viper.Unmarshal(&config)
fmt.Printf("Server: %s:%d\n", config.Server.Host, config.Server.Port)
fmt.Printf("Database: %s\n", config.Database.DSN)
fmt.Printf("App: %s v%s\n", config.App.Name, config.App.Version)
}
配置优先级
📖 Viper 配置来源优先级
// Viper 配置优先级 (从高到低):
// 1. 显式调用 Set
// 2. 命令行参数 (flag)
// 3. 环境变量
// 4. 配置文件
// 5. 远程配置 (etcd/Consul)
// 6. 默认值
func main() {
// 6. 设置默认值 (最低优先级)
viper.SetDefault("server.host", "localhost")
viper.SetDefault("server.port", 8080)
// 4. 读取配置文件
viper.SetConfigName("config")
viper.ReadInConfig()
// 3. 绑定环境变量
viper.BindEnv("server.host", "SERVER_HOST")
viper.BindEnv("server.port", "SERVER_PORT")
viper.AutomaticEnv() // 自动绑定所有环境变量
// 2. 绑定命令行参数
var port int
flag.IntVar(&port, "port", 0, "Server port")
flag.Parse()
viper.BindPFlag("server.port", flag.Lookup("port"))
// 1. 显式设置 (最高优先级)
viper.Set("server.host", "127.0.0.1")
// 获取最终值
host := viper.GetString("server.host")
}
配置热重载
📝 监听配置变更
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigFile("./configs/config.yaml")
viper.ReadInConfig()
// 监听配置变更
viper.WatchConfig()
// 注册变更回调
viper.OnConfigChange(func(e viper.ConfigEvent) {
fmt.Printf("Config changed: %s\n", e.Key)
// 重新加载配置到结构体
var config Config
viper.Unmarshal(&config)
fmt.Printf("New config: %+v\n", config)
})
// 保持程序运行
select {}
}
多环境配置
📝 环境分离配置
package main
import (
"fmt"
"os"
"github.com/spf13/viper"
)
func LoadConfig(env string) (*Config, error) {
// 根据环境加载不同配置
configFile := fmt.Sprintf("./configs/config.%s.yaml", env)
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var config Config
viper.Unmarshal(&config)
return &config, nil
}
func main() {
// 从环境变量获取环境名
env := os.Getenv("APP_ENV")
if env == "" {
env = "development"
}
config, err := LoadConfig(env)
if err != nil {
panic(err)
}
fmt.Printf("Loaded config for %s environment\n", env)
}
远程配置中心
📝 从 etcd/Consul 读取配置
package main
import (
"github.com/spf13/viper"
"github.com/spf13/viper/remote"
)
func main() {
// 从 etcd 读取配置
err := viper.AddRemoteProvider(
"etcd",
"http://localhost:2379",
"/config/app",
)
if err != nil {
panic(err)
}
viper.SetConfigType("yaml")
if err := viper.ReadRemoteConfig(); err != nil {
panic(err)
}
// 从 Consul 读取配置
// viper.AddRemoteProvider("consul", "http://localhost:8500", "/config/app")
// 监听远程配置变更
go func() {
for {
viper.WatchRemoteConfig()
// 配置已变更,重新加载
viper.ReadRemoteConfig()
}
}()
}
最佳实践
✅ Viper 使用建议
- 配置结构体: 使用结构体定义配置,类型安全
- 多环境分离: dev/test/prod 使用不同配置文件
- 环境变量: 敏感信息使用环境变量
- 默认值: 为关键配置设置默认值
- 配置验证: 启动时验证配置有效性
- 热重载: 生产环境启用配置热重载
🚨 常见陷阱
- 并发安全: Viper 读取是并发安全的,但 Write 不是
- 类型转换: GetInt/GetString 等会进行类型转换
- 嵌套访问: 点号访问只支持一层 mapstructure
- 环境变量: 环境变量名默认大写
- 远程配置: 需要额外安装 remote 包