淺談Golang如何使用Viper進(jìn)行配置管理
前言
應(yīng)用程序里要用到很多配置,比如監(jiān)聽端口、數(shù)據(jù)庫(kù)相關(guān)配置、緩存相關(guān)配置等。這些配置可能來(lái)自不同的源,最常見的是本地配置文件,也可以作為命令行參數(shù)或環(huán)境變量傳入,還可以托管在遠(yuǎn)程配置中心。同一項(xiàng)配置可能在多個(gè)不同的配置源中同時(shí)存在,如何處理優(yōu)先級(jí)?在程序運(yùn)行過程中,有些配置需要能夠動(dòng)態(tài)更新,又該如何實(shí)現(xiàn)?配置的源、優(yōu)先級(jí)及動(dòng)態(tài)更新,就是配置管理的內(nèi)容。
在Golang
生態(tài)中,Viper是一個(gè)不錯(cuò)的開源配置管理框架。
配置層級(jí)
Viper
支持多種配置源,并處理了它們之間的優(yōu)先級(jí)問題,按優(yōu)先級(jí)從高到低的順序,層級(jí)如下:
- 命令行參數(shù)
- 環(huán)境變量
- 本地配置文件
- 遠(yuǎn)程配置文件
- 默認(rèn)值
下面分別看一下Viper
是如何支持這些配置源的。
0. 配置結(jié)構(gòu)
以如下的yaml
格式配置文件來(lái)說(shuō)明:
# myconfig.yaml aaa: foo xxx: bbb: bar ccc: 100
Viper
在內(nèi)存中是用map
類型來(lái)維護(hù)配置的,以配置名作為key、配置值作為Value。
為方便使用,我們可以定義如下的結(jié)構(gòu)體來(lái)接收配置:
package config var Config config type config struct { Aaa string Xxx struct { Bbb string Ccc uint } }
1. 命令行參數(shù)
Viper
提供了BindPFlags
函數(shù)來(lái)綁定命令行參數(shù),以Cobra
命令行框架為例,初始化代碼如下:
// 1. 根據(jù)需要定義命令行參數(shù) pflags = rootCmd.PersistentFlags() pflags.StringP("aaa", "a", "", "aaa's usage") pflags.String("xxx.bbb", "", "xxx.bbb's usage") pflags.Uint("xxx.ccc", 0, "xxx.ccc's usage") // 2. Viper綁定命令行參數(shù) viper.BindPFlags(pflags)
這樣就可以通過命令行參數(shù)來(lái)傳入相關(guān)配置:
// 參數(shù)aaa使用長(zhǎng)參數(shù)格式 $ mycmd --aaa foo --xxx.bbb bar --xxx.ccc 100 // 參數(shù)aaa使用短參數(shù)格式 $ mycmd -a foo --xxx.bbb bar --xxx.ccc 100
2. 環(huán)境變量
Viper
提供了AutomaticEnv
函數(shù)來(lái)從環(huán)境變量中加載配置,示例代碼如下:
// 設(shè)置環(huán)境變量的前綴 viper.SetEnvPrefix("MYCMD") // 將配置Key中的點(diǎn)號(hào)(.)和橫杠(-)替換為下劃線(_) viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) // 從環(huán)境變量中讀取匹配的配置 viper.AutomaticEnv()
這樣就可以通過環(huán)境變量來(lái)傳入相關(guān)配置:
$ MYCMD_AAA=foo MYCMD_XXX_BBB=bar MYCMD_XXX_CCC=100 mycmd
3. 本地配置文件
Viper
提供了從本地配置文件加載配置的方法,示例代碼如下:
// 在初始化cobra時(shí),將本地配置文件路徑定義為命令行參數(shù) pflags.StringVarP(&cfgFile, "config", "c", "", "config file") // 在初始化viper時(shí),設(shè)置本地配置文件路徑 viper.SetConfigFile(cfgFile) // 讀取本地配置文件 if err := viper.ReadInConfig(); err != nil { log.Fatal(err) }
這樣就可以通過本地配置文件來(lái)傳入相關(guān)配置:
// myconf.yaml為同目錄下的配置文件,內(nèi)容如上文所述 $ mycmd --config myconf.yaml
4. 遠(yuǎn)程配置文件
Viper
還支持將配置文件托管在遠(yuǎn)程配置中心,如etcd
,示例代碼如下:
// 注意:在實(shí)際項(xiàng)目中,以下參數(shù)也可以作為配置傳入 provider := "etcd3" endpoint := "x.x.x.x:2379" path := "/config/mycmd/myconf.yaml" viper.AddRemoteProvider(provider, endpoint, path) viper.SetConfigType("yaml") if err := viper.ReadRemoteConfig(); err != nil { log.Println("Read remote config failed:", err) }
只要將myconf.yaml配置文件上傳到etcd
的指定路徑下,程序就可以遠(yuǎn)程讀取了。
$ cat myconf.yaml | etcdctl put /config/mycmd/myconf.yaml
5. 默認(rèn)值
Viper
還支持設(shè)置默認(rèn)值,對(duì)于在以上所有源中都不存在的配置,將使用其默認(rèn)值,示例代碼如下:
viper.SetDefault("aaa", "foo") viper.SetDefault("xxx.bbb", "bar") viper.SetDefault("xxx.ccc", 100)
在所有配置源都加載后,我們可以將Viper配置裝載到配置結(jié)構(gòu)體變量中,方便使用:
if err := viper.Unmarshal(&config.Config); err != nil { log.Fatal("Viper unmarshal failed:", err) }
配置動(dòng)態(tài)更新
以上解決了靜態(tài)配置管理的問題,如何實(shí)現(xiàn)配置的動(dòng)態(tài)更新呢?
配置的動(dòng)態(tài)更新包括三個(gè)步驟:
- 更新配置源中的配置
- 更新內(nèi)存中的配置
- 配置的動(dòng)態(tài)生效
注意:如Viper
官方文檔所提,Viper
本身不是并發(fā)安全的,在實(shí)現(xiàn)配置動(dòng)態(tài)更新時(shí),要注意采用鎖機(jī)制等方式來(lái)保證Viper并發(fā)讀寫的安全。
1. 更新配置源中的配置
在Viper
支持的配置源中,命令行參數(shù)、環(huán)境變量是在進(jìn)程啟動(dòng)時(shí)一次性讀取的,不支持動(dòng)態(tài)更新。本地配置文件和遠(yuǎn)程配置文件可以支持動(dòng)態(tài)更新,直接修改配置文件即可。
2. 更新內(nèi)存中的配置
Viper
支持監(jiān)聽本地配置文件的變化,同時(shí)允許注冊(cè)文件變化時(shí)的回調(diào)函數(shù),借助這個(gè)功能可以實(shí)現(xiàn)本地配置文件的動(dòng)態(tài)更新,示意代碼如下:
viper.OnConfigChange(func(e fsnotify.Event) { // 回調(diào)函數(shù) // 可在此處更新內(nèi)存中的本地配置: // 1. 重新讀取本地配置:ReadInConfig // 2. 裝載到配置結(jié)構(gòu)體:Unmarshal // 注意:為Viper及配置結(jié)構(gòu)體加鎖 }) viper.WatchConfig()
對(duì)于遠(yuǎn)程配置文件,可以通過周期性地重新讀取來(lái)實(shí)現(xiàn)動(dòng)態(tài)更新,示意代碼如下:
go func() { for { time.Sleep(time.Second * 10) // 周期為10s // 可在此處更新內(nèi)存中的遠(yuǎn)程配置: // 1. 重新讀取遠(yuǎn)程配置:WatchRemoteConfig // 2. 裝載到配置結(jié)構(gòu)體:Unmarshal // 注意:為Viper及配置結(jié)構(gòu)體加鎖 } }()
3. 配置的動(dòng)態(tài)生效
不同配置的使用方式不同,動(dòng)態(tài)生效方式也就不同,大概可以分為兩類:
- 有些配置,每次使用時(shí)都是讀取最新的值,因此只要更新了內(nèi)存中的配置,就會(huì)即時(shí)生效
- 有些配置,比如數(shù)據(jù)庫(kù)憑據(jù),可能需要重建連接池才能生效
在這里就不再細(xì)說(shuō)了。
總結(jié)
本文介紹了在Golang
中優(yōu)雅、靈活地實(shí)現(xiàn)配置管理的一種方式,涵蓋配置的源、優(yōu)先級(jí)及動(dòng)態(tài)更新等內(nèi)容。重點(diǎn)說(shuō)明了命令行參數(shù)、環(huán)境變量、本地配置文件、遠(yuǎn)程配置文件等多種配置源的實(shí)現(xiàn)方法,以及配置動(dòng)態(tài)更新的實(shí)現(xiàn)思路。
以上就是淺談Golang如何使用Viper進(jìn)行配置管理的詳細(xì)內(nèi)容,更多關(guān)于Go Viper配置管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang實(shí)現(xiàn)并發(fā)數(shù)控制的方法
下面小編就為大家分享一篇golang實(shí)現(xiàn)并發(fā)數(shù)控制的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2017-12-12go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)基本操作詳解
這篇文章主要為大家介紹了go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)基本操作詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型
這篇文章主要介紹了Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型,根據(jù)Go泛型使用的三步曲提到的:類型參數(shù)化、定義類型約束、類型實(shí)例化我們一步步來(lái)定義我們的緩存結(jié)構(gòu)體,需要的朋友可以參考下2022-07-07Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn)
go語(yǔ)言是一門功能強(qiáng)大的編程語(yǔ)言,它提供了眾多的網(wǎng)絡(luò)編程庫(kù),其中包括tcp/ip,本文主要介紹了Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以來(lái)了解一下2024-06-06完美解決go Fscanf 在讀取文件時(shí)出現(xiàn)的問題
這篇文章主要介紹了完美解決go Fscanf 在讀取文件時(shí)出現(xiàn)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2021-03-03GoLang職責(zé)鏈模式代碼實(shí)現(xiàn)介紹
這篇文章主要介紹了GoLang職責(zé)鏈模式代碼實(shí)現(xiàn),職責(zé)鏈模式是一種常用的設(shè)計(jì)模式,可以提高代碼的靈活性與可維護(hù)性,職責(zé)鏈模式將請(qǐng)求和處理分離,可以讓請(qǐng)求在處理鏈中依次經(jīng)過多個(gè)處理者,直到找到能夠處理請(qǐng)求的處理者為止2023-05-05Golang實(shí)現(xiàn)EasyCache緩存庫(kù)實(shí)例探究
這篇文章主要為大家介紹了Golang實(shí)現(xiàn)EasyCache緩存庫(kù)實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01Golang使用gzip壓縮字符減少redis等存儲(chǔ)占用的實(shí)現(xiàn)
本文主要介紹了Golang使用gzip壓縮字符減少redis等存儲(chǔ)占用的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01