配置中心
问题
微服务中如何实现配置的集中管理和动态更新?
答案
本地配置: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: 配置热更新要注意什么?
答案:
- 并发安全:用
atomic.Value或sync.RWMutex保护 - 配置校验:更新前验证新配置有效性
- 回滚机制:保留旧配置,更新失败时回退
- 通知机制:配置变更后通知相关组件重新初始化
Q2: Nacos vs etcd 做配置中心?
答案:
- etcd:Go 原生、轻量、K8s 生态、适合 Go 微服务
- Nacos:功能丰富(配置+服务发现+分组+灰度)、Dashboard 好、Java 生态更成熟
Go 微服务推荐 etcd,已有 Java 微服务的混合架构用 Nacos。