熱更新配置檔案

haoc7發表於2019-09-04

想要使程式在不重啟的前提下更新配置,探索了以下幾種方式:

  • 訊號量觸發更新

  • 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 協議》,轉載必須註明作者和本文連結

相關文章