在 Go 中如何使用 Viper 来管理配置 1

简介: 在 Go 中如何使用 Viper 来管理配置

Viper 是一个功能齐全的 Go 应用程序配置库,支持很多场景。它可以处理各种类型的配置需求和格式,包括设置默认值、从多种配置文件和环境变量中读取配置信息、实时监视配置文件等。无论是小型应用还是大型分布式系统,Viper 都可以提供灵活而可靠的配置管理解决方案。在本文中,我们将深入探讨 Viper 的各种用法和使用场景,以帮助读者更好地了解和使用 Viper 来管理应用程序配置。

为什么选择 Viper

当我们在做技术选型时,肯定要知道为什么选择某一项技术,而之所以选择使用 Viper 来管理应用程序的配置,Viper 官方给出了如下答案:

当构建应用程序时,你不想担心配置文件格式,只想专注于构建出色的软件。Viper 就是为了帮助我们解决这个问题而存在的。

Viper 可以完成以下工作:

  1. 查找、加载和反序列化 JSON、TOML、YAML、HCL、INI、envfile 或 Java Properties 格式的配置文件。
  2. 为不同的配置选项设置默认值。
  3. 为通过命令行标志指定的选项设置覆盖值。
  4. 提供别名系统,以便轻松重命名配置项而不破坏现有代码。
  5. 可以轻松区分用户提供的命令行参数或配置文件中的值是否与默认值相同。

注:关于上面第 5 点,我个人理解的使用场景是:

  1. 先从命令行参数或配置文件中读取配置。
  2. 可以使用 viper.IsSet(key) 方法判断用户是否设置了 key 所对应的 value,如果设置了,可以通过 viper.Get(key) 获取值。
  3. 调用 viper.SetDefault(key, default_value) 来设置默认值(默认值不会覆盖上一步所获取到的值)。
    在第 2 步中可以拿到用户设置的值 value,在第 3 步中可以知道默认值 default_value,这样其实就可以判断两者是否相同了。

Viper 采用以下优先级顺序来加载配置,按照优先级由高到低排序如下:

  • 显式调用 viper.Set 设置的配置值
  • 命令行参数
  • 环境变量
  • 配置文件
  • key/value 存储
  • 默认值

注意 ⚠️:Viper 配置中的键不区分大小写,如 user/User/USER 被视为是相等的 key,关于是否将其设为可选,目前还在讨论中。

Viper 包中最核心的两个功能是:如何把配置值读入 Viper 和从 Viper 中读取配置值,接下来我将分别介绍这两个功能。

把配置值读入 Viper

Viper 支持多种方式读入配置:

  • 设置默认配置值
  • 从配置文件读取配置
  • 监控并重新读取配置文件
  • io.Reader 读取配置
  • 从环境变量读取配置
  • 从命令行参数读取配置
  • 从远程 key/value 存储读取配置

我们一个一个来看。

设置默认配置值

一个好的配置系统应该支持默认值。Viper 支持使用 viper.SetDefault(key, value)key 设置默认值 value,在没有通过配置文件、环境变量、远程配置或命令行标志设置 key 所对应值的情况下,这很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"fmt"
"github.com/spf13/viper"
)
funcmain() {
// 设置默认配置
	viper.SetDefault("username", "jianghushinian")
	viper.SetDefault("server", map[string]string{"ip": "127.0.0.1", "port": "8080"})
// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("Username")) // key 不区分大小写
	fmt.Printf("server: %+v\n", viper.Get("server"))
}

执行以上示例代码得到如下输出:

1
2
3
$ go run main.go
username: jianghushinian
server: map[ip:127.0.0.1 port:8080]

从配置文件读取配置

Viper 支持从 JSON、TOML、YAML、HCL、INI、envfile 或 Java Properties 格式的配置文件中读取配置。Viper 可以搜索多个路径,但目前单个 Viper 实例只支持单个配置文件。Viper 不会默认配置任何搜索路径,将默认决定留给应用程序

主要有两种方式来加载配置文件:

  • 通过 viper.SetConfigFile() 指定配置文件,如果配置文件名中没有扩展名,则需要使用 viper.SetConfigType() 显式指定配置文件的格式。
  • 通过 viper.AddConfigPath() 指定配置文件的搜索路径中,可以通过多次调用,来设置多个配置文件搜索路径。然后通过 viper.SetConfigName() 指定不带扩展名的配置文件,Viper 会根据所添加的路径顺序查找配置文件,如果找到就停止查找。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main
import (
"errors"
"flag"
"fmt"
"github.com/spf13/viper"
)
var (
	cfg = flag.String("c", "", "config file.")
)
funcmain() {
	flag.Parse()
if *cfg != "" {
		viper.SetConfigFile(*cfg)   // 指定配置文件(路径 + 配置文件名)
		viper.SetConfigType("yaml") // 如果配置文件名中没有扩展名,则需要显式指定配置文件的格式
	} else {
		viper.AddConfigPath(".")             // 把当前目录加入到配置文件的搜索路径中
		viper.AddConfigPath("$HOME/.config") // 可以多次调用 AddConfigPath 来设置多个配置文件搜索路径
		viper.SetConfigName("cfg")           // 指定配置文件名(没有扩展名)
	}
// 读取配置文件
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			fmt.Println(errors.New("config file not found"))
		} else {
			fmt.Println(errors.New("config file was found but another error was produced"))
		}
return
	}
	fmt.Printf("using config file: %s\n", viper.ConfigFileUsed())
// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

假如有如下配置文件 config.yaml 与示例程序在同一目录中:

1
2
3
4
5
username:jianghushinian
password:123456
server:
ip:127.0.0.1
port:8080

执行以上示例代码得到如下输出:

1
2
3
$ go run main.go -c ./config.yaml 
using config file: ./config.yaml
username: jianghushinian

监控并重新读取配置文件

Viper 支持在应用程序运行过程中实时读取配置文件,即热加载配置。

只需要调用 viper.WatchConfig() 即可开启此功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
"fmt"
"time"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
funcmain() {
	viper.SetConfigFile("./config.yaml")
	viper.ReadInConfig()
// 注册每次配置文件发生变更后都会调用的回调函数
	viper.OnConfigChange(func(e fsnotify.Event) {
		fmt.Printf("config file changed: %s\n", e.Name)
	})
// 监控并重新读取配置文件,需要确保在调用前添加了所有的配置路径
	viper.WatchConfig()
// 阻塞程序,这个过程中可以手动去修改配置文件内容,观察程序输出变化
	time.Sleep(time.Second * 10)
// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

值得注意的是,在调用 viper.WatchConfig() 监控并重新读取配置文件之前,需要确保添加了所有的配置路径。

并且,我们还可以通过 viper.OnConfigChange() 函数注册一个每次配置文件发生变更后都会调用的回调函数。

我们依然使用上面的 config.yaml 配置文件:

1
2
3
4
5
username:jianghushinian
password:123456
server:
ip:127.0.0.1
port:8080

执行以上示例代码,并在程序阻塞的时候,手动修改配置文件中 username 所对应的值为 江湖十年,可以得到如下输出:

1
2
3
$ go run main.go
config file changed: config.yaml
username: 江湖十年

io.Reader 读取配置

Viper 支持从任何实现了 io.Reader 接口的配置源中读取配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import (
"bytes"
"fmt"
"github.com/spf13/viper"
)
funcmain() {
	viper.SetConfigType("yaml") // 或者使用 viper.SetConfigType("YAML")
var yamlExample = []byte(`
username: jianghushinian
password: 123456
server:
  ip: 127.0.0.1
  port: 8080
`)
	viper.ReadConfig(bytes.NewBuffer(yamlExample))
// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

这里我们通过 bytes.NewBuffer() 构造了一个 bytes.Buffer 对象,它实现了 io.Reader 接口,所以可以直接传递给 viper.ReadConfig() 来从中读取配置。

执行以上示例代码得到如下输出:

1
2
$ go run main.go
username: jianghushinian

从环境变量读取配置

Viper 还支持从环境变量读取配置,有 5 个方法可以帮助我们使用环境变量:

  • AutomaticEnv():可以绑定全部环境变量(用法上类似 flag 包的 flag.Parse())。调用后,Viper 会自动检测和加载所有环境变量。
  • BindEnv(string...) : error:绑定一个环境变量。需要一个或两个参数,第一个参数是配置项的键名,第二个参数是环境变量的名称。如果未提供第二个参数,则 Viper 将假定环境变量名为:环境变量前缀_键名,且为全大写形式。例如环境变量前缀为 ENV,键名为 username,则环境变量名为 ENV_USERNAME。当显式提供第二个参数时,它不会自动添加前缀,也不会自动将其转换为大写。例如,使用 viper.BindEnv("username", "username") 绑定键名为 username 的环境变量,应该使用 viper.Get("username") 读取环境变量的值。
    在使用环境变量时,需要注意,每次访问它的值时都会去环境变量中读取。当调用 BindEnv 时,Viper 不会固定它的值。
  • SetEnvPrefix(string):可以告诉 Viper 在读取环境变量时使用的前缀。BindEnvAutomaticEnv 都将使用此前缀。例如,使用 viper.SetEnvPrefix("ENV") 设置了前缀为 ENV,并且使用 viper.BindEnv("username") 绑定了环境变量,在使用 viper.Get("username") 读取环境变量时,实际读取的 keyENV_USERNAME
  • SetEnvKeyReplacer(string...) *strings.Replacer:允许使用 strings.Replacer 对象在一定程度上重写环境变量的键名。例如,存在 SERVER_IP="127.0.0.1" 环境变量,使用 viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) 将键名中的 .- 替换成 _,则通过 viper.Get("server_ip")viper.Get("server.ip")viper.Get("server-ip") 三种方式都可以读取环境变量对应的值。
  • AllowEmptyEnv(bool):当环境变量为空时(有键名而没有值的情况),默认会被认为是未设置的,并且程序将回退到下一个配置来源。要将空环境变量视为已设置,可以使用此方法。

注意 ⚠️:Viper 在读取环境变量时,是区分大小写的。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
"fmt"
"strings"
"github.com/spf13/viper"
)
funcmain() {
	viper.SetEnvPrefix("env") // 设置读取环境变量前缀,会自动转为大写 ENV
	viper.AllowEmptyEnv(true) // 将空环境变量视为已设置
	viper.AutomaticEnv()      // 可以绑定全部环境变量
	viper.BindEnv("username") // 也可以单独绑定某一个环境变量
	viper.BindEnv("password")
// 将键名中的 . 或 - 替换成 _
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
// 读取配置
	fmt.Printf("username: %v\n", viper.Get("username"))
	fmt.Printf("password: %v\n", viper.Get("password"))
	fmt.Printf("server.ip: %v\n", viper.Get("server.ip"))
// 读取全部配置,只能获取到通过 BindEnv 绑定的环境变量,无法获取到通过 AutomaticEnv 绑定的环境变量
	fmt.Println(viper.AllSettings())
}

执行以上示例代码得到如下输出:

1
2
3
4
5
$ ENV_USERNAME=jianghushinian ENV_SERVER_IP=127.0.0.1 ENV_PASSWORD= go run main.go
username: jianghushinian
password: 
server.ip: 127.0.0.1
map[password: username:jianghushinian]

从命令行参数读取配置

Viper 支持 pflag 包(它们其实都在 spf13 仓库下),能够绑定命令行标志,从而读取命令行参数。

BindEnv 类似,在调用绑定方法时,不会设置值,而是在每次访问时设置。这意味着我们可以随时绑定它,例如可以在 init() 函数中。

  • BindPFlag:对于单个标志,可以调用此方法进行绑定。
  • BindPFlags:可以绑定一组现有的标志集 pflag.FlagSet

示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import (
"fmt"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
var (
	username = pflag.StringP("username", "u", "", "help message for username")
	password = pflag.StringP("password", "p", "", "help message for password")
)
funcmain() {
	pflag.Parse()
	viper.BindPFlag("username", pflag.Lookup("username")) // 绑定单个标志
	viper.BindPFlags(pflag.CommandLine)                   // 绑定标志集
// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
	fmt.Printf("password: %s\n", viper.Get("password"))
}

执行以上示例代码得到如下输出:

1
2
3
$ go run main.go -u jianghushinian -p 123456
username: jianghushinian
password: 123456

因为 pflag 能够兼容标准库的 flag 包,所以我们也可以变相的让 Viper 支持 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import (
"flag"
"fmt"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
funcmain() {
	flag.String("username", "", "help message for username")
	pflag.CommandLine.AddGoFlagSet(flag.CommandLine) // 将 flag 命令行参数注册到 pflag
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)
// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

执行以上示例代码得到如下输出:

1
2
$ go run main.go --username jianghushinian
username: jianghushinian

如果你不使用 flag 或 pflag,则 Viper 还提供了 Go 接口的形式来支持其他 Flags,具体用法可以参考官方文档

从远程 key/value 存储读取配置

要在 Viper 中启用远程支持,需要匿名导入 viper/remote 包:

1
import _ "github.com/spf13/viper/remote"

Viper 支持 etcd、Consul 等远程 key/value 存储,这里以 Consul 为例进行讲解。

首先需要准备 Consul 环境,最方便快捷的方式就是启动一个 Docker 容器:

1
2
3
4
5
6
$ docker run \
    -d \
    -p 8500:8500 \
    -p 8600:8600/udp \
    --name=badger \
    consul agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0

Docker 容器启动好后,浏览器访问 http://localhost:8500/,即可进入 Consul 控制台,在 user/config 路径下编写 YAML 格式的配置。

Consul Config

使用 Viper 从 Consul 读取配置示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import (
"fmt"
"github.com/spf13/viper"
	_ "github.com/spf13/viper/remote"// 必须导入,才能加载远程 key/value 配置
)
funcmain() {
	viper.AddRemoteProvider("consul", "localhost:8500", "user/config") // 连接远程 consul 服务
	viper.SetConfigType("YAML")                                        // 显式设置文件格式文 YAML
	viper.ReadRemoteConfig()
// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
	fmt.Printf("server.ip: %s\n", viper.Get("server.ip"))
}

执行以上示例代码得到如下输出:

1
2
3
$ go run main.go
username: jianghushinian
server.ip: 127.0.0.1

笔记:如果你想停止通过 Docker 安装的 Consul 容器,则可以执行 docker stop badger 命令。如果需要删除,则可以执行 docker rm badger 命令。

相关文章
|
4月前
|
算法 Java Go
运行时管理GO与Java的概要对比
【5月更文挑战第17天】本文介绍Go、Python和Java的运行时机制各异。Go是编译型语言,其runtime负责内存管理、GC和协程调度,强调性能和低延迟。Java的JVM兼顾跨平台和性能,使用字节码和JIT编译,其GC策略复杂且高效。三种语言在设计和优化上各有侧重,适用不同场景。
64 3
|
1月前
|
JSON 缓存 监控
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
Viper 是一个强大的 Go 语言配置管理库,适用于各类应用,包括 Twelve-Factor Apps。相比仅支持 `.ini` 格式的 `go-ini`,Viper 支持更多配置格式如 JSON、TOML、YAML
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
|
1月前
|
Linux Go
Linux——windows10下的Ubuntu18.04安装并配置go环境
Linux——windows10下的Ubuntu18.04安装并配置go环境
35 1
|
20天前
|
Unix 编译器 Go
|
2月前
|
JSON Go API
go项目实现通过配置文件进行配置项统一管理
go项目实现通过配置文件进行配置项统一管理
19 0
|
3月前
|
Go
go配置镜像(阿里云、七牛)
go配置镜像(阿里云、七牛)
162 1
|
2月前
|
Go 持续交付
使用 Makefile 管理和部署 Go 项目
在软件开发中,`Makefile` 用于自动化任务,提升效率。在Go项目中,它简化构建和部署。`Makefile`集成了编译、打包、清理和部署等任务,减少错误,提高效率。通过定义规则和依赖,`make`工具执行任务。示例展示了如何创建`Makefile`进行Go应用的自动化部署,包括构建、传输、停启服务。通过`make deploy-dev`一键执行部署流程。`Makefile`不仅简化部署,还可扩展实现更多复杂自动化,提升开发流程的专业性和效率。
39 0
|
3月前
|
Go
Go环境配置
【6月更文挑战第11天】
69 0
|
4月前
|
Go
配置go语言下载包 - 蓝易云
这个命令会将包下载到你的GOPATH目录下,并自动安装它。
89 1
|
4月前
|
IDE Go 开发工具
【GO基础】2. IDEA配置Go语言开发环境
【GO基础】2. IDEA配置Go语言开发环境
344 2