Viper 是一個用于 Go 應用程序的配置管理庫,支持多種配置格式和數據源。
安裝依賴
go get github.com/spf13/viper
go get github.com/spf13/viper/remote
go get go.etcd.io/etcd/client/v3
"github.com/spf13/viper/remote"
要寫在etcd客戶端import里
1. 配置格式—就是可以讀的配置類型
Viper 支持多種常見的配置格式,包括:
- JSON: 一種輕量級的數據交換格式,易于閱讀和編寫。
- YAML: 一種人類可讀的數據序列化格式,常用于配置文件。
- TOML: 一種易于閱讀的配置文件格式,旨在成為最小的配置文件格式。
- HCL: HashiCorp 配置語言,用于描述基礎設施配置。
- Java Properties: 一種簡單的鍵值對格式,常用于 Java 應用程序。
- INI: 一種簡單的配置文件格式,常用于 Windows 應用程序。
- Envfile: 環境變量文件格式,通常用于存儲環境變量。
2. 支持的數據源—可以從哪里導入要讀的配置
Viper 不僅支持從文件中讀取配置,還支持從多種數據源獲取配置,包括:
- 文件: 從本地文件系統中讀取配置文件。
- 環境變量: 從操作系統的環境變量中讀取配置。
- io.Reader: 從Reader中讀取配置。
- 遠程配置系統: 從遠程配置系統(如 etcd、Consul)中讀取配置。
Viper 支持的四種常見數據源的簡單實現:
1. 文件
Viper 支持從多種文件格式中讀取配置,包括 JSON、YAML、TOML、HCL、INI 等。
使用方法
- 設置配置文件路徑和名稱:
viper.SetConfigName("config") // 配置文件名稱(不帶擴展名) viper.SetConfigType("yaml") // 配置文件類型(如 yaml、json 等) viper.AddConfigPath(".") // 配置文件搜索路徑
- 讀取配置文件:
if err := viper.ReadInConfig(); err != nil {log.Fatalf("Error reading config file: %s", err) }
示例
假設有一個 config.yaml
文件:
database:host: localhostport: 5432
讀取配置:
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {log.Fatal(err)
}
fmt.Println("Database Host:", viper.GetString("database.host"))
fmt.Println("Database Port:", viper.GetInt("database.port"))
2. 環境變量
Viper 可以從操作系統的環境變量中讀取配置,適合在容器化或云原生環境中使用。
使用方法
- 啟用環境變量支持:
viper.AutomaticEnv() // 自動綁定環境變量
- 設置環境變量前綴(可選):
viper.SetEnvPrefix("MYAPP") // 環境變量前綴(如 MYAPP_DATABASE_HOST)
- 綁定特定鍵到環境變量:
viper.BindEnv("database.host", "DB_HOST") // 將 database.host 綁定到環境變量 DB_HOST
示例
假設設置了環境變量:
export DB_HOST=localhost
export DB_PORT=5432
讀取配置:
viper.AutomaticEnv()
viper.BindEnv("database.host", "DB_HOST")
viper.BindEnv("database.port", "DB_PORT")
fmt.Println("Database Host:", viper.GetString("database.host"))
fmt.Println("Database Port:", viper.GetInt("database.port"))
3. io.Reader
Viper 支持從實現了 io.Reader
接口的對象中讀取配置,適合從內存或網絡流中加載配置。
使用方法
- 從
io.Reader
讀取配置:configData := []byte(`{"database": {"host": "localhost", "port": 5432}}`) reader := bytes.NewReader(configData) viper.SetConfigType("json") // 設置配置類型 if err := viper.ReadConfig(reader); err != nil {log.Fatal(err) }
示例
configData := []byte(`
database:host: localhostport: 5432
`)
reader := bytes.NewReader(configData)
viper.SetConfigType("yaml")
if err := viper.ReadConfig(reader); err != nil {log.Fatal(err)
}
fmt.Println("Database Host:", viper.GetString("database.host"))
fmt.Println("Database Port:", viper.GetInt("database.port"))
4. etcd
Viper 支持從 etcd(分布式鍵值存儲)中讀取配置,適合分布式系統的配置管理。
使用方法
- 安裝 etcd 支持:
go get github.com/spf13/viper/remote
- 配置 etcd 客戶端:
import ("github.com/spf13/viper"_ "github.com/spf13/viper/remote" )
- 從 etcd 讀取配置:
viper.AddRemoteProvider("etcd", "http://127.0.0.1:2379", "/config/path") viper.SetConfigType("yaml") // 設置配置類型 if err := viper.ReadRemoteConfig(); err != nil {log.Fatal(err) }
示例
假設 etcd 中存儲了以下配置:
database:host: localhostport: 5432
讀取配置:
viper.AddRemoteProvider("etcd", "http://127.0.0.1:2379", "/config/path")
viper.SetConfigType("yaml")
if err := viper.ReadRemoteConfig(); err != nil {log.Fatal(err)
}
fmt.Println("Database Host:", viper.GetString("database.host"))
fmt.Println("Database Port:", viper.GetInt("database.port"))
總結
- 文件:適合本地開發和靜態配置。
- 環境變量:適合容器化或云原生環境。
- io.Reader:適合從內存或網絡流中加載配置。
- etcd:適合分布式系統的動態配置管理。
綜合go案例
config.env
env=dev
server.ip=127.0.0.1
server.port=8080
courses=["golang", "C/C++", "音視頻", "kernel", "dpdk", "面試題", "游戲"]
config.json
{"env": "dev","server": {"ip":"127.0.0.1","port": 8085},"courses": ["golang","C/C++","音視頻","kernel","dpdk","面試題","游戲"],"list": [{"name": "golang","author": "nick"},{"name": "C/C++","author": "king"},{"name": "kernel","author": "vico"},{"name": "音視頻","author": "Darren"},{"name": "游戲","author": "mark"}]
}
config.noext
# 鍵值
env: dev
# 對象或map
server:ip: 127.0.0.1port: 8080
# 數組或列表
courses:- "golang"- "C/C++"- "音視頻"- "kernel"- "dpdk"- "面試題"- "游戲"list:- name: golangauthor: nick- name: C/C++author: king- name: kernelauthor: vico- name: 音視頻author: Darren- name: 游戲author: mark
config.yaml
# 鍵值
env: dev
# 對象或map
server:ip: 127.0.0.1port: 8080
# 數組或列表
courses:- "golang"- "C/C++"- "音視頻"- "kernel"- "dpdk"- "面試題"- "游戲"list:- name: golangauthor: nick- name: C/C++author: king- name: kernelauthor: vico- name: 音視頻author: Darren- name: 游戲author: mark
data_source.go
package configimport ("github.com/spf13/viper"_ "github.com/spf13/viper/remote""io""log"
)// LoadFromFile 加載配置文件。
// 該函數使用 vipers 去從指定的文件路徑加載配置。
// 參數:
//
// filepath - 配置文件的路徑。
// typ - 可選參數,指定配置文件的類型。
//
// 返回值:
//
// *viper.Viper - 加載了配置文件數據的 viper 實例。
// error - 如果加載配置文件時發生錯誤,返回該錯誤。
func LoadFromFile(filepath string, typ ...string) (*viper.Viper, error) {// 創建一個新的 viper 實例。v := viper.New()// 設置配置文件的路徑。v.SetConfigFile(filepath)// 如果提供了配置文件類型,則設置配置文件類型。if len(typ) > 0 {v.SetConfigType(typ[0])}// 讀取并解析配置文件到viper中。err := v.ReadInConfig()// 返回 viper 實例和可能的錯誤。return v, err
}// LoadFromEnv 初始化一個viper實例,并將其配置為自動從環境變量中加載配置。
// 該函數返回一個指向viper實例的指針,以及一個錯誤(此示例中未實現錯誤處理)。
func LoadFromEnv() (*viper.Viper, error) {// 創建一個新的viper實例。v := viper.New()// 啟用自動環境變量加載,viper將自動查找與結構體字段同名的環境變量。//v.AutomaticEnv()// 手動綁定環境變量GOPATH和GOROOT到viper實例。// 這樣做是為了方便地從環境變量中讀取這些值,而無需手動查詢環境變量。v.BindEnv("GOPATH")v.BindEnv("GOROOT")// 返回viper實例指針,以及nil作為錯誤值,表示沒有發生錯誤。return v, nil
}// LoadFromIoReader 從 io.Reader 類型的參數中加載配置信息。
// 此函數主要用于從不同的讀取源(如文件、網絡流等)中加載配置,
// 并根據提供的 typ 參數設置配置的類型。
// 參數:
// - reader: io.Reader 類型的接口,代表任何可以讀取字節流的對象。
// - typ: 字符串,指定配置文件的類型(例如 "json"、"yaml")。
//
// 返回值:
// - *viper.Viper: 一個指向 viper.Viper 實例的指針,用于進一步操作或獲取配置信息。
// - error: 在讀取配置過程中遇到的錯誤(如果有)。
func LoadFromIoReader(reader io.Reader, typ string) (*viper.Viper, error) {// 創建一個新的 viper 實例。v := viper.New()// 設置配置類型,根據傳入的 typ 參數。v.SetConfigType(typ)// 從 reader 參數指定的源中讀取配置信息。err := v.ReadConfig(reader)// 返回 viper 實例和可能的錯誤。return v, err
}// LoadFromEtcd loadFromEtcd 從 Etcd 中加載配置信息。
// 參數:
//
// etcdAddr: Etcd服務器的地址。
// key: 配置在Etcd中的鍵路徑。
// typ: 配置文件的類型。
//
// 返回值:
//
// *viper.Viper: 加載配置后的Viper實例指針。
// error: 錯誤信息,如果有的話。
func LoadFromEtcd(etcdAddr, key, typ string) (*viper.Viper, error) {// 創建一個新的Viper實例。v := viper.New()// 為Viper實例添加Etcd3遠程提供者。// 這里可能會出現錯誤,如果提供的Etcd地址或鍵路徑無效。err := v.AddRemoteProvider("etcd3", etcdAddr, key)if err != nil {// 記錄錯誤信息。log.Println(err)// 返回nil和錯誤信息。return nil, err}// 設置Viper實例的配置類型。v.SetConfigType(typ)// 從遠程提供者讀取配置信息。// 這里可能會出現錯誤,如果無法從遠程提供者獲取配置信息。err = v.ReadRemoteConfig()// 返回Viper實例和可能的錯誤信息。return v, err
}
main.go
package mainimport ("bytes""context""fmt"clientv3 "go.etcd.io/etcd/client/v3""golang20-viper/config""log""os"
)func main() {//loadFile()//loadEnv()//loadReader()loadEtcd()
}// loadFile 函數演示了如何從不同格式的配置文件中加載配置。
// 它依次嘗試加載 .env, .json, .yaml, .toml 和 .yaml 文件,并打印出特定配置項的值。
func loadFile() {// 從 "config.env" 文件加載配置,并打印出環境、服務器端口和課程信息。v1, err := config.LoadFromFile("config.env", "env")fmt.Println("config.env", err, v1.Get("env"), v1.Get("server.port"), v1.Get("courses"))// 從 "config.json" 文件加載配置,包括環境、服務器端口、課程信息和作者信息。v2, err := config.LoadFromFile("config.json")fmt.Println("config.json", err, v2.Get("env"), v2.Get("server.port"), v2.Get("courses").([]any)[0], v2.Get("list").([]any)[0].(map[string]any)["author"])// 從 "config.noext" 文件加載 YAML 格式的配置,同樣打印環境、服務器端口、課程信息和作者信息。v3, err := config.LoadFromFile("config.noext", "yaml")fmt.Println("config.noext", err, v3.Get("env"), v3.Get("server.port"), v3.Get("courses").([]any)[0], v3.Get("list").([]any)[0].(map[string]any)["author"])// 從 "config.toml" 文件加載配置,展示如何獲取嵌套配置項的值。v4, err := config.LoadFromFile("config.toml")fmt.Println("config.toml", err, v4.Get("env"), v4.Get("server.port"), v4.Get("courses").(map[string]any)["list"].([]any)[0], v4.Get("list").([]any)[0].(map[string]any)["author"])// 從 "config.yaml" 文件加載配置,再次打印出環境、服務器端口、課程信息和作者信息,驗證不同格式文件的兼容性。v5, err := config.LoadFromFile("config.yaml")fmt.Println("config.yaml", err, v5.Get("env"), v5.Get("server.port"), v5.Get("courses").([]any)[0], v5.Get("list").([]any)[0].(map[string]any)["author"])
}func loadEnv() {v, err := config.LoadFromEnv()fmt.Println(err, v.Get("GOROOT"), v.Get("gopath"))
}// loadReader 讀取配置文件并解析特定配置項。
// 該函數沒有輸入參數和返回值。
// 功能描述:
// 1. 讀取名為 "config.yaml" 的配置文件。
// 2. 如果讀取過程中遇到錯誤,記錄錯誤信息并終止程序運行。
// 3. 使用 bytes.NewReader 創建一個字節流讀取器來讀取配置文件內容。
// 4. 調用 config.LoadFromIoReader 函數從字節流讀取器中加載配置信息。
// 5. 打印解析后的配置項,包括環境變量、服務器端口、課程信息和作者信息。
func loadReader() {// 讀取配置文件 "config.yaml" 的內容到 byteList。byteList, err := os.ReadFile("config.yaml")if err != nil {// 如果讀取配置文件時發生錯誤,記錄錯誤信息并終止程序。log.Fatalln(err)}// 創建一個新的字節流讀取器來讀取配置文件內容。r := bytes.NewReader(byteList)// 從字節流讀取器中加載配置信息,并處理可能的錯誤。v, err := config.LoadFromIoReader(r, "yaml")// 打印解析后的配置項。fmt.Println("io.reader", err, v.Get("env"), v.Get("server.port"), v.Get("courses").([]any)[0], v.Get("list").([]any)[0].(map[string]any)["author"])
}// loadEtcd 函數用于從本地文件加載配置并寫入到 etcd 中,然后從 etcd 中讀取配置并打印部分配置項。
// 該函數不接收任何參數,也不返回任何值。
func loadEtcd() {// 定義 etcd 的地址、配置項的鍵以及本地配置文件的路徑etcdAddr := "192.168.88.131:2379"key := "/0voice/viper/config.yaml"loaclFilepath := "config.yaml"// 將本地配置文件的內容寫入到 etcd 中writeConfToEtcd(etcdAddr, key, loaclFilepath)// 從 etcd 中加載配置,并解析為 yaml 格式v, err := config.LoadFromEtcd(etcdAddr, key, "yaml")// 打印從 etcd 中讀取的配置項,包括環境、服務器端口、課程列表中的第一個課程以及列表中的第一個作者的名稱fmt.Println("etcd", err, v.Get("env"), v.Get("server.port"), v.Get("courses").([]any)[0], v.Get("list").([]any)[0].(map[string]any)["author"])//fmt.Println(err, v)
}// writeConfToEtcd 將本地配置文件內容寫入到etcd指定鍵中
// 參數說明:
// - etcdAddr: etcd服務地址,格式為"IP:PORT"
// - key: etcd中存儲配置的鍵名
// - localFilepath: 本地配置文件的路徑
//
// 功能說明:
//
// 讀取配置文件,將其內容存入etcd集群的指定鍵中
// 遇到任何錯誤(文件讀取、連接etcd、寫入etcd)將直接終止程序
func writeConfToEtcd(etcdAddr, key, localFilepath string) {// 從固定路徑讀取配置文件內容byteList, err := os.ReadFile(localFilepath)if err != nil {// 如果讀取配置文件時發生錯誤,記錄錯誤信息并終止程序。log.Fatalln(err)}v := string(byteList)// 創建etcd客戶端連接cli, err := clientv3.New(clientv3.Config{Endpoints: []string{etcdAddr},})if err != nil {log.Fatal(err)}// 將配置內容寫入etcd指定鍵_, err = cli.Put(context.Background(), key, v)if err != nil {log.Fatal(err)}
}
etcd docker部署
docker run -d \
-p 2379:2379 \
-p 2380:2380 \
--restart always \
--volume=/home/etcd:/etcd-data \
--name etcd quay.io/coreos/etcd:v3.5.7 \
/usr/local/bin/etcd \
--data-dir=/etcd-data --name node1 \
--initial-advertise-peer-urls http://192.168.239.161:2380 \
--listen-peer-urls http://0.0.0.0:2380 \
--advertise-client-urls http://192.168.239.161:2379 \
--listen-client-urls http://0.0.0.0:2379 \
--initial-cluster node1=http://192.168.239.161:2380 \
--initial-cluster-token tkn \
--initial-cluster-state new
https://github.com/0voice