想要使程式在不重啟的前提下更新配置,探索了以下幾種方式:
-
訊號量觸發更新
-
API手動觸發更新
-
監聽檔案觸發更新
-
使用第三方包
對系統程式呼叫監聽,當接收到 syscal.SIGHUP 或者 syscal.SIGUSR1 訊號時,呼叫reload() 函式對 config 進行重新讀取賦值。
核心程式碼:
hup := make(chan os.Signal)
signal.Notify(hup, syscall.SIGHUP)
go func() {
for {
select {
case <-hup:
if err := reload(conf); err != nil {
log.Errorf("Error reloading config: %s", err)
}
}
}
}()
對系統預留RESTful API,使用curl -x POST 方式觸發,reload()更新。
核心程式碼:
hup := make(chan bool)
go func() {
<- hup
for {
select {
case <-hup:
if err := reload(conf); err != nil {
log.Errorf("Error reloading config: %s", err)
}
}
}
}()
利用ticker每隔一段時間檢查config是否更新,如果更新則重新解析config。
此方式中有兩種實現:
lock
atomic
lock 程式碼:
func (c *Config) parse() bool {
fname, _ := os.Stat(c.Filename)
c.LastModifyTime = fname.ModTime().Unix()
f, err := ioutil.ReadFile(c.Filename)
if err != nil {
log.Println(err)
return false
}
data := new(MySQL)
err = json.Unmarshal(f, &data)
if err != nil {
log.Println(err)
return false
}
c.Mt.Lock()
c.MySQL = data
c.Mt.Unlock()
log.Printf("data: %+v\n", c.MySQL)
return true
}
func (c *Config) reload() {
ticker := time.NewTicker(time.Second * 3)
for {
select {
case <-ticker.C:
f, _ := os.Stat(c.Filename)
curModifyTime := f.ModTime().Unix()
if curModifyTime > c.LastModifyTime {
if c.parse() {
log.Println("loading...")
}
}
}
}
}
線上執行地址:lock 方式
atomic 程式碼:
var atoConfig atomic.Value
func (c *Config) reload() {
ticker := time.NewTicker(time.Second * 3)
for {
select {
case <-ticker.C:
f, _ := os.Stat(c.Filename)
curModifyTime := f.ModTime().Unix()
if curModifyTime > c.LastModifyTime {
mysql := read(c.Filename)
if mysql != nil {
atoConfig.Store(mysql)
chwr <- true
}
}
}
}
}
func (c *Config) write() {
data := atoConfig.Load().(*MySQL)
if data == nil {
return
}
c.MySQL = data
c.lastTime()
}
線上執行地址:atomic 方式
有很多第三方包提供了比較完整的功能,比如go-micro/config、viper等。
viper的特性
設定預設值
可以讀取如下格式的配置檔案:JSON、TOML、YAML、HCL
監控配置檔案改動,並熱載入配置檔案
從環境變數讀取配置
從遠端配置中心讀取配置(etcd/consul),並監控變動
從命令列 flag 讀取配置
從快取中讀取配置
支援直接設定配置項的值
從切合現有專案方面,使用監聽檔案變化的atomic方式。程式碼更改比較少,也滿足現有需求。
本作品採用《CC 協議》,轉載必須註明作者和本文連結