通常我们更新应用程序的配置文件,都需要手动重启程序或手动重新加载配置。假设一组服务部署在10台机器上,你需要借助批量运维工具执行重启命令,而且10台同时重启可能还会造成服务短暂不可用。要是更新配置后,服务自动刷新配置多好...今天我们就用go实现配置文件热加载的小功能,以后更新配置再也不用手动重启了...
1.基本思路
通常应用程序启动的流程:加载配置,然后run()。我们怎么做到热加载呢?我们的思路是这样的:
【1】在加载配置文件之后,启动一个线程。
【2】该线程定时监听这个配置文件是否有改动。
【3】如果配置文件有变动,就重新加载一下。
【4】重新加载之后通知需要使用这些配置的应用程序(进程或线程),实际上就是刷新内存中配置。
2.加载配置
首先我们要实现加载配置功能。假设配置文件是k=v格式的,如下:
那我们得写一个解析配置的包了...让我们一起面向对象:
filename string 配置文件名称
data map[string]string 将配置文件中的k/v解析存放到map中
lastModifyTime int64 记录配置文件上一次更改时间
rwLock sync.RWMutex 读写锁,处理这样一种竞争情况:更新这个结构体时其他线程正在读取改结构体中的内容,后续用到的时候会讲
notifyList []Notifyer 存放所有观察者,此处我们用到了观察者模式,也就是需要用到这个配置的对象,我们就把它加到这个切片。当配置更新之后,通知切片中的对象配置更新了。
接下来我们可以给这个结构体添加一些方法了:
2.1 构造函数
1 func NewConfig(file string)(conf *Config, err error){
2 conf = &Config{
3 filename: file,
4 data: make(map[string]string, 1024),
5 }
6
7 m, err := conf.parse()
8 if err != nil {
9 fmt.Printf("parse conf error:%v\n", err)
10 return
11 }
12
13 // 将解析配置文件后的数据更新到结构体的map中,写锁
14 conf.rwLock.Lock()
15 conf.data = m
16 conf.rwLock.Unlock()
17
18 // 启一个后台线程去检测配置文件是否更改
19 go conf.reload()
20 return
21}
构造函数做了三件事:
【1】初始化Config
【2】调用parse()函数,解析配置文件,并把解析后的map更新到Config
【3】启动一个线程,准确说是启动一个goroutine,即reload()
注意此处更新map时加了写锁了,目的在于不影响拥有读锁的线程读取数据。
2.2 parse()
解析函数比较简单,主要是读取配置文件,一行行解析,数据存放在map中。
1 func (c *Config) parse() (m map[string]string, err error) {
2 // 如果在parse()中定义一个map,这样就是一个新的map不用加锁
3 m = make(map[string]string, 1024)
4
5 f, err := os.Open(c.filename)
6 if err != nil {
7