說(shuō)到配置文件熱加載,這個(gè)功能在很多框架中都提供了,如beego,實(shí)現(xiàn)的效果就是當(dāng)你修改文件后,會(huì)把你修改后的配置重新加載到配置文件中,而不用重啟程序,這個(gè)功能在日常中還是非常實(shí)用的,畢竟很多時(shí)候,線上的配置文件不是想改就能改的。
這次就自己實(shí)現(xiàn)一個(gè)配置文件的熱加載功能的包,并通過(guò)一個(gè)簡(jiǎn)單的例子對(duì)完成的包進(jìn)行使用驗(yàn)證
配置文件熱加載包的是實(shí)現(xiàn)
其實(shí)整體的思路還是比較簡(jiǎn)單的,當(dāng)獲取配置文件內(nèi)容后,會(huì)開啟一個(gè)goroutine,去 循環(huán)讀配置文件,當(dāng)然這里不可能不限制的一直循環(huán),而是設(shè)置了一個(gè)定時(shí)器,定時(shí)去讀文件,根據(jù)文件的修改時(shí)間是否變化,從而確定是否重新reload配置文件
實(shí)現(xiàn)的config 包的文件結(jié)構(gòu)為:
1
2
|
├── config.go └── config_notify.go |
config.go:代碼的主要處理邏輯
config_notify.go:主要定義了一個(gè)接口,用于當(dāng)文件修改時(shí)間變化的時(shí)候執(zhí)行回調(diào)
config_notify.go的代碼相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,我們先看看這個(gè)代碼:
1
2
3
4
5
|
package config // 定義一個(gè)通知的接口 type Notifyer interface { Callback(*Config) } |
這樣當(dāng)我們實(shí)現(xiàn)了Callback這個(gè)方法的時(shí)候,我們就實(shí)現(xiàn)了Notifyer這個(gè)接口,具體的調(diào)用在后面會(huì)說(shuō)
在config.go中我們頂一個(gè)了一個(gè)結(jié)構(gòu)體:
1
2
3
4
5
6
7
|
type Config struct { filename string lastModifyTime int64 data map[string]string rwLock sync.RWMutex notifyList []Notifyer } |
結(jié)構(gòu)體中主要包含幾個(gè)字段:
filename:配置文件名字
lastModifyTime:配置文件的最后修改時(shí)間
data:用于將從配置文件中讀取的內(nèi)容存儲(chǔ)為map
rwlock:讀寫鎖
notifyList:用于將調(diào)用該包的程序追加到切片中,用于通知調(diào)用上面在config_notify.go定義的callback回調(diào)函數(shù)
關(guān)于讀取配置文件中的內(nèi)容并存儲(chǔ)到map中,這里定義了一個(gè)方法實(shí)現(xiàn):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
func (c *Config) parse()(m map[string]string,err error){ // 讀文件并或?qū)⑽募械臄?shù)據(jù)以k/v的形式存儲(chǔ)到map中 m = make(map[string]string,1024) file,err := os.Open(c.filename) if err != nil{ return } var lineNo int reader := bufio.NewReader(file) for { // 一行行的讀文件 line,errRet := reader.ReadString( '\n' ) if errRet == io.EOF{ // 表示讀到文件的末尾 break } if errRet != nil{ // 表示讀文件出問(wèn)題 err = errRet return } lineNo++ line = strings.TrimSpace(line) // 取出空格 if len(line) == 0 || line[0] == '\n' || line[0] == '+' || line[0] == ';' { // 當(dāng)前行為空行或者是注釋行等 continue } arr := strings.Split(line, "=" ) // 通過(guò)=進(jìn)行切割取出k/v結(jié)構(gòu) if len(arr) == 0{ fmt.Printf( "invalid config,line:%d\n" ,lineNo) continue } key := strings.TrimSpace(arr[0]) if len(key) == 0{ fmt.Printf( "invalid config,line:%d\n" ,lineNo) continue } if len(arr) == 1{ m[key] = "" continue } value := strings.TrimSpace(arr[1]) m[key] = value } return } |
而最后我們就需要一個(gè)定時(shí)器,每隔一段時(shí)間判斷配置文件的最后修改時(shí)間是否變化,如果變化則重新讀取一次文件并將文件內(nèi)容存儲(chǔ)到map中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
func (c *Config) reload(){ // 這里啟動(dòng)一個(gè)定時(shí)器,每5秒重新加載一次配置文件 ticker := time.NewTicker(time.Second*5) for _ = range ticker.C{ func(){ file,err := os.Open(c.filename) if err != nil{ fmt.Printf( "open %s failed,err:%v\n" ,c.filename,err) return } defer file.Close() fileInfo,err := file.Stat() if err != nil{ fmt.Printf( "stat %s failed,err:%v\n" ,c.filename,err) return } curModifyTime := fileInfo.ModTime().Unix() fmt.Printf( "%v --- %v\n" ,curModifyTime,c.lastModifyTime) //判斷文件的修改時(shí)間是否大于最后一次修改時(shí)間 if curModifyTime > c.lastModifyTime{ m,err := c.parse() if err != nil{ fmt.Println( "parse failed,err:" ,err) return } c.rwLock.Lock() c.data = m c.rwLock.Unlock() for _, n:=range c.notifyList{ n.Callback(c) } c.lastModifyTime = curModifyTime } }() } |
關(guān)于config完整的代碼地址:https://github.com/pythonsite/go_simple_code/tree/master/config
一個(gè)演示上述包的例子
這里一個(gè)簡(jiǎn)單的例子,代碼的邏輯也非常簡(jiǎn)單就是寫一個(gè)循環(huán)從配置文件讀取配置信息,當(dāng)然這里是為了測(cè)試效果,寫成了循環(huán)。這里有個(gè)問(wèn)題需要注意,就是在配置文件中存放數(shù)據(jù)的時(shí)候應(yīng)該是如下格式存儲(chǔ)
1
2
3
4
|
listen_addr = localhost server_port = 1000 # Nginx addr nginx_addr = 192.168.1.2:9090 |
測(cè)試代碼的主要結(jié)構(gòu)如下:
├── config.conf
└── main.go
config.conf為配置文件
main.go 為主要測(cè)試代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
type AppConfig struct { port int nginxAddr string } type AppconfigMgr struct { config atomic.Value } var appConfigMgr = &AppconfigMgr{} func(a *AppconfigMgr)Callback(conf *config.Config){ var appConfig = &AppConfig{} port,err := conf.GetInt( "server_port" ) if err != nil{ fmt.Println( "get port failed,err:" ,err) return } appConfig.port = port fmt.Println( "port:" ,appConfig.port) nginxAddr,err := conf.GetString( "nginx_addr" ) if err != nil{ fmt.Println( "get nginx addr failed,err:" ,err) return } appConfig.nginxAddr = nginxAddr fmt.Println( "nginx addr :" ,appConfig.nginxAddr) appConfigMgr.config.Store(appConfig) } func run(){ for { // 每5秒打印一次數(shù)據(jù),查看自己更改配置文件后是否可以熱刷新 appConfig := appConfigMgr.config.Load().(*AppConfig) fmt.Println( "port:" ,appConfig.port) fmt.Println( "nginx addr:" ,appConfig.nginxAddr) time.Sleep(5* time.Second) } } func main() { conf,err := config.NewConfig( "/Users/zhaofan/go_project/src/go_dev/13/config_test/config.conf" ) if err != nil{ fmt.Println( "parse config failed,err:" ,err) return } //打開文件獲取內(nèi)容后,將自己加入到被通知的切片中 conf.AddNotifyer(appConfigMgr) var appConfig = &AppConfig{} appConfig.port,err = conf.GetInt( "server_port" ) if err != nil{ fmt.Println( "get port failed,err:" ,err) return } fmt.Println( "port:" ,appConfig.port) appConfig.nginxAddr,err = conf.GetString( "nginx_addr" ) if err != nil{ fmt.Println( "get nginx addr failed,err:" ,err) return } fmt.Println( "nginx addr:" ,appConfig.nginxAddr) appConfigMgr.config.Store(appConfig) run() } |
上面代碼中有一段代碼非常重要:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func(a *AppconfigMgr)Callback(conf *config.Config){ var appConfig = &AppConfig{} port,err := conf.GetInt( "server_port" ) if err != nil{ fmt.Println( "get port failed,err:" ,err) return } appConfig.port = port fmt.Println( "port:" ,appConfig.port) nginxAddr,err := conf.GetString( "nginx_addr" ) if err != nil{ fmt.Println( "get nginx addr failed,err:" ,err) return } appConfig.nginxAddr = nginxAddr fmt.Println( "nginx addr :" ,appConfig.nginxAddr) appConfigMgr.config.Store(appConfig) } |
這里我們實(shí)現(xiàn)了Callback方法,同時(shí)就實(shí)現(xiàn)了我們?cè)赾onfig包中定義的那個(gè)接口
測(cè)試效果如下,當(dāng)我們更改配置文件后,程序中的配置文件也被重新加載
完整的測(cè)試代碼地址:https://github.com/pythonsite/go_simple_code/tree/master/config_test
總結(jié)
以上所述是小編給大家介紹的使用Go語(yǔ)言實(shí)現(xiàn)配置文件熱加載功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!
原文鏈接:https://www.cnblogs.com/zhaof/p/8593204.html