中文 | English
通过 gRPC 实现的简单 Go 远程配置库。
基于 C/S 架构,客户端和服务端之间通过 gRPC 进行通信。
由服务端 Manager 集中管理配置,在 Manager 启动前,注册每一个配置到 Manager 中。
客户端 LocalManager 负责从服务端 Manager 获取配置。
支持实时监听配置更新。
go get github.com/l0neman/go-configfunc TestManager() {
log.If(configManagerLogTag, "version: %s", GetVersion())
configManager, err := getConfigManager()
if err != nil {
log.Eln(configManagerLogTag, "初始化失败 > ", err)
return
}
// 使用配置结构体注册配置,仅支持结构体指针和结构体数组指针
err = configManager.RegisterConfig(
&AppMaster{},
&[]SwitchSsh{},
)
if err != nil {
log.Eln(configManagerLogTag, "注册配置失败 > ", err)
return
}
// 初始化配置管理器,设置模式
err = configManager.Init(ModeDefault)
if err != nil {
log.Eln(configManagerLogTag, "初始化配置管理器失败 > ", err)
return
}
// 使用配置对象来获取对应配置
var masterConfig AppMaster
err = configManager.GetConfig(&masterConfig)
if err != nil {
log.Eln(configManagerLogTag, "获取配置失败 >", err)
return
}
log.If(configManagerLogTag, "master 配置 > %+v", masterConfig)
var switchSshTest []SwitchSsh
err = configManager.GetConfig(&switchSshTest)
if err != nil {
log.Eln(configManagerLogTag, "获取配置失败 >", err)
return
}
log.If(configManagerLogTag, "switch ssh config 配置 > %+v", switchSshTest)
err = configManager.ClearConfig(&switchSshTest)
if err != nil {
log2.Println("清除配置失败 >", err)
}
startService(configManager)
}
func startService(configManager ConfigManager) {
// 启动配置服务
listener, err := net.Listen("tcp", ":8772")
if err != nil {
log.Eln(configManagerLogTag, "监听失败 > ", err)
return
}
log.Df(configManagerLogTag, "8772 端口监听启动")
server := grpc.NewServer()
// 注册配置服务到 gRPC 服务上
RegisterGrpcServer(configManager, server)
err = server.Serve(listener)
if err != nil {
log.Eln(configManagerLogTag, err)
}
}
// helper
func getConfigManager() (ConfigManager, error) {
EnableDebug()
// 创建配置服务端,提供数据库连接参数 (支持 MySQL 或 SQLite)
// MySQL 示例
rdbms := NewMySqlConfig("127.0.0.1", "3306", "root", "123456")
// SQLite 示例
// rdbms := NewSqliteConfig("go_config.db")
configManager, err := NewManager(NewConfig(rdbms))
if err != nil {
log.Eln(configManagerLogTag, "初始化失败 > ", err)
return nil, err
}
return configManager, nil
}func TestLocalManager() {
log.Iln("version: ", GetVersion())
localConfigManager := getLocalManager()
// 注册配置
_ = localConfigManager.RegisterConfig(&AppMaster{})
// 初始化本地配置管理器
err := localConfigManager.Init(ModeDefault)
if err != nil {
log.Eln(configManagerLogTag, "初始化失败 > ", err)
return
}
appMaster := &AppMaster{}
// 获取配置
err = localConfigManager.GetConfig(appMaster)
if err != nil {
log.Eln(configManagerLogTag, "获取配置失败 >", err)
return
}
log.If(configManagerLogTag, "appMaster 配置 > %+v", appMaster)
appMaster.Data.Influx = "1.1.1.1:8086"
// 更新配置
err = localConfigManager.SetConfig(appMaster)
if err != nil {
log.Eln(configManagerLogTag, "写入配置失败 >", err)
}
err = localConfigManager.GetConfig(appMaster)
if err != nil {
log.Eln(configManagerLogTag, "获取配置失败 >", err)
return
}
log.If(configManagerLogTag, "appMaster 配置 2 > %+v", appMaster)
}
func getLocalManager() ConfigManager {
EnableDebug()
// 创建配置客户端,提供服务端地址,默认端口 8772,支持自定义端口,例如 0.0.0.0:8888
localConfigManager := NewLocalManager("0.0.0.0")
return localConfigManager
}对于服务端 Manager 来说,远程代表数据库 (MySQL 或 SQLite)
对于客户端 LocalManager 来说,远程代表 Manager
ModeDefault 默认模式,首先从远程获取配置,获取不到就从本地获取配置,
本地获取不到就生成配置模板并退出程序,从本地获取到配置,会写入远程(如果远程是数据库)。
SetConfig 会写入远程
ModeRemote 仅从远程获取配置,获取不到就报错
SetConfig 会写入远程
ModeLocal 仅从本地获取配置,获取不到就报错
SetConfig 不会写入远程
要支持从 Manager 获取某个配置,需要先注册配置。
默认以结构体名称转化为小写加下划线作为配置的唯一 key,切片或数组类型则以类型增加 _list 后缀为唯一 key。
也可以为结构体提供 Key 函数来自定义配置的 key。
每一个基本类型的配置字段都支持使用 default 标签设置默认值。
默认值会用于生成默认配置文件,以及和 GetConfig 时生效,为空的字段会被填充默认值。
下面是 master 的配置结构体示例。
type AppMaster struct {
Flag struct {
AppMode string `yaml:"app_mode" default:"ib"`
SwitchType string `yaml:"switch_type" default:"ruijie"`
HttpPort string `yaml:"http_port" default:"8765"`
IbmStorageApi string `yaml:"ibm_storage_api" default:"https://{host}:{port}/scalemgmt/v2/filesystems;{username}/{password}"`
} `yaml:"flag"`
Metric struct {
RunPowerVal int `yaml:"run_power_val" default:"80"`
OneServerGpuCnt int `yaml:"one_server_gpu_cnt" default:"8"`
} `yaml:"metric"`
Debug struct {
Log bool `yaml:"log" default:"false"`
Pprof bool `yaml:"pprof" default:"false"`
} `yaml:"debug"`
Data struct {
TimeSeriesDb string `yaml:"time_series_db" default:"influxdb"`
Influx string `yaml:"influx" default:"{host}:{port}"`
InfluxPeriodDays int `yaml:"influx_period_days" default:"2"`
Taos string `yaml:"taos" default:"{host}:{port}@{user}/{pwd}"`
} `yaml:"data"`
Report struct {
Feishu string `yaml:"feishu" default:"{feishu_link}"`
Trap string `yaml:"trap" default:"{host}:{port}"`
ServerFanMinRpm int `yaml:"server_fan_min_rpm" default:"-1"`
CheckDisk string `yaml:"check_disk" default:"/"`
} `yaml:"report"`
Legacy struct {
Master string `yaml:"master" default:"{host}"`
UseOldDb bool `yaml:"use_old_db" default:"false"`
} `yaml:"legacy"`
Collector struct {
SonicSwitch bool `yaml:"sonic_switch" default:"false"`
SonicSwitchGrpcPort int `yaml:"sonic_switch_grpc_port" default:"-1"`
} `yaml:"collector"`
Email struct {
Sender string `yaml:"sender" default:"{email}"`
} `yaml:"email"`
}
func (v *AppMaster) Key() string {
return "go_master_new"
}// 获取版本号
GetVersion()
// 开启调试
EnableDebug()
// 创建 MySQL 配置
NewMySqlConfig(host, port, user, pwd)
// 创建 SQLite 配置
NewSqliteConfig(path)
// 创建管理器配置包装
NewConfig(rdbms)
// 创建服务端管理器
NewManager(config)
ConfigManager
// RegisterConfig 注册配置,提供结构体指针
// providers 配置结构体对象,用于提供 key 及默认值
RegisterConfig(providers ...any) error
// UnregisterConfig 解注册配置
UnregisterConfig(providers ...any) error
// Init 初始化 Manager,需在获取/设置配置之前调用
Init(mode Mode) error
// GetConfig 提供结构体接收指针,获取配置
GetConfig(receiver any) error
// SetConfig 提供结构体指针,设置配置
SetConfig(config any) error
// ClearConfig 提供结构体指针,清除配置
ClearConfig(provider any) error
// WatchConfig 监听配置变化
// provider 配置结构体对象,callback 用于回调配置
WatchConfig(provider any, callback func(config any)) (canceler func(), err error)
// gRPC 服务器注册
RegisterGrpcServer(manager *Manager, server *grpc.Server)func TestManager() {
configManager, err := getConfigManager()
if err != nil {
log.Eln(configManagerLogTag, "初始化失败 > ", err)
return
}
// 注册配置
err = configManager.RegisterConfig(
&AppMaster{},
&[]SwitchSsh{},
)
if err != nil {
log.Eln(configManagerLogTag, "注册配置失败 > ", err)
return
}
// 初始化配置管理器
err = configManager.Init(ModeDefault)
if err != nil {
log.Eln(configManagerLogTag, "初始化失败 > ", err)
return
}
var masterConfig AppMaster
// 获取配置
err = configManager.GetConfig(&masterConfig)
if err != nil {
log.Eln(configManagerLogTag, "获取配置失败 >", err)
return
}
log.If(configManagerLogTag, "master 配置 > %+v", masterConfig)
// 监听配置变更
canceler, err := configManager.WatchConfig(&AppMaster{}, func(config any) {
// 转换为对应的配置指针
master := config.(*AppMaster)
log.If(configManagerLogTag, "AppMaster 配置变更 > %+v", master.Flag)
})
defer canceler()
if err != nil {
log.Eln(configManagerLogTag, "监听配置失败 > ", err)
return
}
// 默认配置变化的情况
go func(manager ConfigManager) {
time.Sleep(time.Second)
i := 1
for {
var appMaster AppMaster
appMaster.Flag.HttpPort = fmt.Sprintf("%d", 8772+i)
// 更新配置后,将会通知到监听器
err := manager.SetConfig(&appMaster)
if err != nil {
log.Eln(configManagerLogTag, err)
}
log.Df(configManagerLogTag, "AppMaster 配置设置 > %+v", appMaster.Flag)
i++
time.Sleep(time.Second * 2)
}
}(configManager)
// 注册 gRPC 配置
startService(configManager)
}本地动态配置管理器分发时,消息不会通知到服务端的 Listener,只会通知到本地注册的 Listener。
func TestLocalManager() {
// 获取本地配置管理器
localManager := getLocalManager()
// 注册配置
err := localManager.RegisterConfig(&AppMaster{})
if err != nil {
log.Eln(configManagerLogTag, "注册配置失败 > ", err)
return
}
// 初始化配置
err = localManager.Init(ModeDefault)
if err != nil {
log.Eln(configManagerLogTag, "初始化失败 > ", err)
return
}
// 获取配置
var appMaster AppMaster
err = localManager.GetConfig(&appMaster)
if err != nil {
log.Eln(configManagerLogTag, "获取配置失败 > ", err)
return
}
log.Df(configManagerLogTag, "AppMaster 配置 > %+v", appMaster)
// 监听配置变更
canceler, err := localManager.WatchConfig(&AppMaster{}, func(config any) {
// 转换为对应的配置指针
master := config.(*AppMaster)
log.If(configManagerLogTag, "AppMaster 配置变更 > %+v", master.Flag.HttpPort)
})
defer func() {
log.Df(configManagerLogTag, "AppMaster 配置变更 取消监听")
canceler()
}()
if err != nil {
log.Eln(configManagerLogTag, "监听配置失败 > ", err)
return
}
select {}
}

