Go中的應(yīng)用配置管理詳解
問(wèn)題
- Go語(yǔ)言在編譯時(shí)不會(huì)將配置文件這類第三方文件打包進(jìn)二進(jìn)制文件中
- 它既受當(dāng)前路徑的影響,也會(huì)因所填寫的不同而改變,并非是絕對(duì)可靠的
解決
命令行參數(shù)
在Go語(yǔ)言中,可以直接通過(guò)flag標(biāo)準(zhǔn)庫(kù)來(lái)實(shí)現(xiàn)該功能。實(shí)現(xiàn)邏輯為,如果存在命令行參數(shù),則優(yōu)先使用命令行參數(shù),否則使用配置文件中的配置參數(shù)。
如下:
var (
port string
runMode string
config string
)
func init() {
// 獲取命令行參數(shù)
err = setupFlag()
if err != nil {
log.Fatalf("init.setupFlag err: %v", err)
}
......
}
// 獲取命令行參數(shù)
func setupFlag() error {
flag.StringVar(&port, "port", "", "啟動(dòng)端口")
flag.StringVar(&runMode, "mode", "", "啟動(dòng)模式")
flag.StringVar(&config, "config", "config/", "指定要使用的配置文件路徑")
flag.Parse()
return nil
}
通過(guò)上述代碼,我們可以通過(guò)標(biāo)準(zhǔn)庫(kù)flag讀取命令行參數(shù),然后根據(jù)其默認(rèn)值判斷配置文件是否存在。若存在,則對(duì)讀取配置的路徑進(jìn)行變更,代碼如下:
package setting
import "github.com/spf13/viper"
type Setting struct {
vp *viper.Viper
}
// 初始化配置文件的基礎(chǔ)屬性
func NewSetting(configs ...string) (*Setting, error) {
vp := viper.New()
vp.SetConfigName("config")
if len(configs) != 0 {
for _, config := range configs {
if config != "" {
vp.AddConfigPath(config)
}
}
} else {
vp.AddConfigPath("configs/")
}
vp.SetConfigType("yaml")
err := vp.ReadInConfig()
if err != nil {
return nil, err
}
return &Setting{vp}, nil
}
接下來(lái),對(duì)ServerSetting配置項(xiàng)進(jìn)行覆寫。如果存在,則覆蓋原有的文件配置,使其優(yōu)先級(jí)更高,代碼如下:
// 初始化配置文件
func setupSetting() error {
setting, err := setting2.NewSetting(strings.Split(config, ",")...)
if err != nil {
return err
}
......
if port != "" {
global.ServerSetting.HttpPort = port
}
if runMode != "" {
global.ServerSetting.RunMode = runMode
}
return nil
}
然后在運(yùn)行的時(shí)候傳入?yún)?shù)即可:
go run main.go -port=8081 -mode=debug -config=configs/
系統(tǒng)環(huán)境變量
可以將配置文件存放在系統(tǒng)自帶的全局變量中,如$HOME/conf或/etc/conf中,這樣做的好處是不需要重新自定義一個(gè)新的系統(tǒng)環(huán)境變量。
一般來(lái)說(shuō),我們會(huì)在程序中內(nèi)置一些系統(tǒng)環(huán)境變量的讀取,其優(yōu)先級(jí)低于命令行參數(shù),但高于文件配置。
打包進(jìn)二進(jìn)制文件
可以將配置文件這種第三方文件打包進(jìn)二進(jìn)制文件中,這樣就不需要過(guò)度關(guān)注這些第三方文件了。但這樣做是有一定代價(jià)的,因此要注意使用的應(yīng)用場(chǎng)景,即并非所有的項(xiàng)目都能這樣操作。
首先安裝go-bindata庫(kù),安裝命令如下:
go get -u github.com/go-bindata/go-bindata/...
通過(guò)go-bindata庫(kù)可以將數(shù)據(jù)文件轉(zhuǎn)換為Go代碼。例如,常見的配置文件、資源文件(如Swagger UI)等都可以打包進(jìn)Go代碼中,這樣就可以“擺脫”靜態(tài)資源文件了。接下來(lái)在項(xiàng)目根目錄下執(zhí)行生成命令:
go-bindata -o configs/config.go -pkg-configs configs/config.yaml
執(zhí)行這條命令后,會(huì)將 configs/config.yaml 文件打包,并通過(guò)-o 選項(xiàng)指定的路徑輸出到configs/config.go文件中,再通過(guò)設(shè)置的-pkg選項(xiàng)指定生成的packagename為configs,接下來(lái)只需執(zhí)行下述代碼,就可以讀取對(duì)應(yīng)的文件內(nèi)容了:
b,_:=configs.Asset("configs/config.yaml")
把第三方文件打包進(jìn)二進(jìn)制文件后,二進(jìn)制文件必然增大,而且在常規(guī)方法下無(wú)法做文件的熱更新和監(jiān)聽,必須要重啟并且重新打包才能使用最新的內(nèi)容,因此這種方式是有利有弊的。
配置熱更新
開源的fsnotify
既然要做配置熱更新,那么首先要知道配置是什么時(shí)候修改的,做了哪些事?因此我們需要對(duì)所配置的文件進(jìn)行監(jiān)聽,只有監(jiān)聽到了,才能知道它做了哪些變更。
開源庫(kù) fsnotify 是用Go語(yǔ)言編寫的跨平臺(tái)文件系統(tǒng)監(jiān)聽事件庫(kù),常用于文件監(jiān)聽,因此我們可以借助該庫(kù)來(lái)實(shí)現(xiàn)這個(gè)功能。
(1)安裝
go get -u golang.org/x/sys/... go get -u github.com/fsnotify/fsnotify
fsnotify是基于golang.org/x/sys實(shí)現(xiàn)的,并非syscall標(biāo)準(zhǔn)庫(kù),因此在安裝的同時(shí)需要更新其版本,確保版本是最新的。
(2)案例
package main
import (
"gopkg.in/fsnotify.v1"
"log"
)
func main() {
watcher, _ := fsnotify.NewWatcher()
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Fatal("event: ", event)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Fatal("error:", err)
}
}
}()
path := "configs/config.yaml"
_ = watcher.Add(path)
<-done
}
通過(guò)監(jiān)聽,我們可以很便捷地知道文件做了哪些變更。進(jìn)一步來(lái)說(shuō),我們可以通過(guò)對(duì)其進(jìn)行二次封裝,在它的上層實(shí)現(xiàn)一些變更動(dòng)作來(lái)完成配置文件的熱更新。
使用viper開源庫(kù)實(shí)現(xiàn)熱更新
viper開源庫(kù)能夠很便捷地實(shí)現(xiàn)對(duì)文件的監(jiān)聽和熱更新。
打開pkg/setting/section.go文件,針對(duì)重載應(yīng)用配置項(xiàng),新增如下處理方法:
var sections = make(map[string]interface{})
// 解析配置文件
func (s *Setting) ReadSection(k string, v interface{}) error {
err := s.vp.UnmarshalKey(k, v)
if err != nil {
return err
}
if _,ok:=sections[k];!ok{
sections[k] = v
}
return nil
}
首先修改ReadSection方法,增加讀取section的存儲(chǔ)記錄,以便在重新加載配置的方法中進(jìn)行二次處理。接下來(lái)新增ReloadAllSection方法,重新讀取配置,代碼如下:
// 讀取所有配置
func (s *Setting) ReadAllSections() error {
for k, v := range sections {
err := s.ReadSection(k, v)
if err != nil {
return err
}
}
return nil
}
// 監(jiān)聽配置變化
func (s *Setting) WatchSettingChange() {
go func() {
s.vp.WatchConfig()
s.vp.OnConfigChange(func(in fsnotify.Event) {
_ = s.ReloadAllSections()
})
}()
}
最后在pkg/setting/setting.go文件中新增文件熱更新的監(jiān)聽和變更處理,代碼如下:
// 初始化配置文件的基礎(chǔ)屬性
func NewSetting(configs ...string) (*Setting, error) {
vp := viper.New()
vp.SetConfigName("config")
for _, config := range configs {
if config != "" {
vp.AddConfigPath(config)
}
}
vp.SetConfigType("yaml")
err := vp.ReadInConfig()
if err != nil {
return nil, err
}
// 加入熱更新
s := &Setting{vp: vp}
s.WatchSettingChange()
return s, nil
}
在上述代碼中,首先在NewSetting方法中起一個(gè)協(xié)程,再在里面通過(guò)WatchConfig方法對(duì)文件配置進(jìn)行監(jiān)聽,并在OnConfigChange方法中調(diào)用剛剛編寫的重載方法ReloadAllSection來(lái)處理熱更新的文件監(jiān)聽事件回調(diào),這樣就可以“悄無(wú)聲息”地實(shí)現(xiàn)一個(gè)文件配置熱更新了。
OnConfigChange方法的回調(diào)方法形參,其實(shí)就是fsnotify。
以上就是Go中的應(yīng)用配置管理詳解的詳細(xì)內(nèi)容,更多關(guān)于Go應(yīng)用配置管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go的固定時(shí)長(zhǎng)定時(shí)器和周期性時(shí)長(zhǎng)定時(shí)器
本文主要介紹了Go的固定時(shí)長(zhǎng)定時(shí)器和周期性時(shí)長(zhǎng)定時(shí)器,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
并發(fā)安全本地化存儲(chǔ)go-cache讀寫鎖實(shí)現(xiàn)多協(xié)程并發(fā)訪問(wèn)
這篇文章主要介紹了并發(fā)安全本地化存儲(chǔ)go-cache讀寫鎖實(shí)現(xiàn)多協(xié)程并發(fā)訪問(wèn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
Windows系統(tǒng)中搭建Go語(yǔ)言開發(fā)環(huán)境圖文詳解
GoLand?是?JetBrains?公司推出的商業(yè)?Go?語(yǔ)言集成開發(fā)環(huán)境(IDE),這篇文章主要介紹了Windows系統(tǒng)中搭建Go語(yǔ)言開發(fā)環(huán)境詳解,需要的朋友可以參考下2022-10-10

