我的名字叫浩仔/配置文件热更新

Created Tue, 24 Sep 2019 07:40:49 +0800 Modified Mon, 13 Dec 2021 10:41:45 +0800
720 Words

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

  • 信号量触发更新

  • 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

1.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...")
				}
			}
		}
	}
}

在线运行地址见文末

2.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()
}

在线运行地址见文末

四、使用第三方包

有很多第三方包提供了比较完整的功能,比如go-micro/config、viper等。

viper的特性

  • 设置默认值

  • 可以读取如下格式的配置文件:JSON、TOML、YAML、HCL

  • 监控配置文件改动,并热加载配置文件

  • 从环境变量读取配置

  • 从远程配置中心读取配置(etcd/consul),并监控变动

  • 从命令行 flag 读取配置

  • 从缓存中读取配置

  • 支持直接设置配置项的值

从简单有效来讲,使用监听文件变化的atomic方式。代码更改比较少,也满足现有需求。

代码地址: https://play.golang.org/p/yUMsqZXlVrx https://play.golang.org/p/dxr6fT_mMR2

JS
Arrow Up