跳到主要内容

配置中心

问题

微服务中如何实现配置的集中管理和动态更新?

答案

本地配置:Viper

import "github.com/spf13/viper"

func initConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./configs")

// 读取配置文件
if err := viper.ReadInConfig(); err != nil {
log.Fatal(err)
}

// 读取环境变量(优先级高于文件)
viper.AutomaticEnv()
viper.SetEnvPrefix("APP")

// 获取配置
port := viper.GetInt("server.port")
dbDSN := viper.GetString("database.dsn")

// 反序列化到结构体
var cfg Config
viper.Unmarshal(&cfg)
}

// 监听文件变更(热更新)
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Println("配置文件更新:", e.Name)
viper.Unmarshal(&cfg)
})

远程配置中心:etcd

// 从 etcd 读取配置
func loadConfigFromEtcd(client *clientv3.Client) (*Config, error) {
resp, err := client.Get(context.Background(), "/config/user-service")
if err != nil {
return nil, err
}

var cfg Config
if err := yaml.Unmarshal(resp.Kvs[0].Value, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}

// Watch 配置变更
func watchConfig(client *clientv3.Client, onUpdate func(*Config)) {
ch := client.Watch(context.Background(), "/config/user-service")
for resp := range ch {
for _, ev := range resp.Events {
if ev.Type == clientv3.EventTypePut {
var cfg Config
yaml.Unmarshal(ev.Kv.Value, &cfg)
onUpdate(&cfg)
}
}
}
}

配置优先级

环境变量 > 命令行参数 > 远程配置中心 > 本地配置文件 > 默认值

线程安全的配置热更新

// 使用 atomic.Value 保证并发安全
var globalConfig atomic.Value

func UpdateConfig(cfg *Config) {
globalConfig.Store(cfg)
}

func GetConfig() *Config {
return globalConfig.Load().(*Config)
}

常见面试问题

Q1: 配置热更新要注意什么?

答案

  1. 并发安全:用 atomic.Valuesync.RWMutex 保护
  2. 配置校验:更新前验证新配置有效性
  3. 回滚机制:保留旧配置,更新失败时回退
  4. 通知机制:配置变更后通知相关组件重新初始化

Q2: Nacos vs etcd 做配置中心?

答案

  • etcd:Go 原生、轻量、K8s 生态、适合 Go 微服务
  • Nacos:功能丰富(配置+服务发现+分组+灰度)、Dashboard 好、Java 生态更成熟

Go 微服务推荐 etcd,已有 Java 微服务的混合架构用 Nacos。

相关链接