热更新配置文件

想要使程序在不重启的前提下更新配置,探索了以下几种方式:

  • 信号量触发更新

  • 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)
      }
    }
  }
}()

API手动触发更新

对系统预留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方式。代码更改比较少,也满足现有需求。

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!