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: localhost
port: 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: localhost
port: 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: localhost
port: 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.1
port: 8080
# 数组或列表
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.yaml
# 键值
env: dev
# 对象或map
server:
ip: 127.0.0.1
port: 8080
# 数组或列表
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
data_source.go
package config
import (
"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 main
import (
"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