使用Go語言實現(xiàn)配置文件熱加載功能
說到配置文件熱加載,這個功能在很多框架中都提供了,如beego,實現(xiàn)的效果就是當你修改文件后,會把你修改后的配置重新加載到配置文件中,而不用重啟程序,這個功能在日常中還是非常實用的,畢竟很多時候,線上的配置文件不是想改就能改的。
這次就自己實現(xiàn)一個配置文件的熱加載功能的包,并通過一個簡單的例子對完成的包進行使用驗證
配置文件熱加載包的是實現(xiàn)
其實整體的思路還是比較簡單的,當獲取配置文件內容后,會開啟一個goroutine,去 循環(huán)讀配置文件,當然這里不可能不限制的一直循環(huán),而是設置了一個定時器,定時去讀文件,根據文件的修改時間是否變化,從而確定是否重新reload配置文件
實現(xiàn)的config 包的文件結構為:
├── config.go └── config_notify.go
config.go:代碼的主要處理邏輯
config_notify.go:主要定義了一個接口,用于當文件修改時間變化的時候執(zhí)行回調
config_notify.go的代碼相對來說比較簡單,我們先看看這個代碼:
package config
// 定義一個通知的接口
type Notifyer interface {
Callback(*Config)
}
這樣當我們實現(xiàn)了Callback這個方法的時候,我們就實現(xiàn)了Notifyer這個接口,具體的調用在后面會說
在config.go中我們頂一個了一個結構體:
type Config struct {
filename string
lastModifyTime int64
data map[string]string
rwLock sync.RWMutex
notifyList []Notifyer
}
結構體中主要包含幾個字段:
filename:配置文件名字
lastModifyTime:配置文件的最后修改時間
data:用于將從配置文件中讀取的內容存儲為map
rwlock:讀寫鎖
notifyList:用于將調用該包的程序追加到切片中,用于通知調用上面在config_notify.go定義的callback回調函數(shù)
關于讀取配置文件中的內容并存儲到map中,這里定義了一個方法實現(xiàn):
func (c *Config) parse()(m map[string]string,err error){
// 讀文件并或將文件中的數(shù)據以k/v的形式存儲到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{
// 表示讀文件出問題
err = errRet
return
}
lineNo++
line = strings.TrimSpace(line) // 取出空格
if len(line) == 0 || line[0] == '\n' || line[0] == '+' || line[0] == ';'{
// 當前行為空行或者是注釋行等
continue
}
arr := strings.Split(line,"=") // 通過=進行切割取出k/v結構
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
}
而最后我們就需要一個定時器,每隔一段時間判斷配置文件的最后修改時間是否變化,如果變化則重新讀取一次文件并將文件內容存儲到map中。
func (c *Config) reload(){
// 這里啟動一個定時器,每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)
//判斷文件的修改時間是否大于最后一次修改時間
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
}
}()
}
關于config完整的代碼地址:https://github.com/pythonsite/go_simple_code/tree/master/config
一個演示上述包的例子
這里一個簡單的例子,代碼的邏輯也非常簡單就是寫一個循環(huán)從配置文件讀取配置信息,當然這里是為了測試效果,寫成了循環(huán)。這里有個問題需要注意,就是在配置文件中存放數(shù)據的時候應該是如下格式存儲
listen_addr = localhost server_port = 1000 # Nginx addr nginx_addr = 192.168.1.2:9090
測試代碼的主要結構如下:
├── config.conf
└── main.go
config.conf為配置文件
main.go 為主要測試代碼
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ù)據,查看自己更改配置文件后是否可以熱刷新
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
}
//打開文件獲取內容后,將自己加入到被通知的切片中
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()
}
上面代碼中有一段代碼非常重要:
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)
}
這里我們實現(xiàn)了Callback方法,同時就實現(xiàn)了我們在config包中定義的那個接口
測試效果如下,當我們更改配置文件后,程序中的配置文件也被重新加載

完整的測試代碼地址:https://github.com/pythonsite/go_simple_code/tree/master/config_test
總結
以上所述是小編給大家介紹的使用Go語言實現(xiàn)配置文件熱加載功能,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復大家的!
相關文章
Go語言如何使用golang-jwt/jwt/v4進行JWT鑒權詳解
最近項目中需要用到鑒權機制,golang中jwt可以用,這篇文章主要給大家介紹了關于Go語言如何使用golang-jwt/jwt/v4進行JWT鑒權的相關資料,需要的朋友可以參考下2022-09-09
golang?db事務的統(tǒng)一封裝的實現(xiàn)
這篇文章主要介紹了golang db事務的統(tǒng)一封裝的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12

