Viper是适用于Go应用程序(包括
Twelve-Factor App)的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。
viper 的特性
- 支持默认值设置
- 支持
json、TOML、YAML、HCL、envfile、Java properties风格的配置文件类型 - 支持可选的实时配置文件监控
- 支持从环境变量读取
- 支持从远程配置系统中读取
- 支持命令行读取
- 支持buffer读取
- 显式配置值
配置的优先级
- 显示调用
Set设置值 - 命令行参数(flag)
- 环境变量
- 配置文件
- 远程key/value存储
- 默认值
注意:配置键的大小写是不敏感的。
配置的读取与加载
在有配置文件的情况下,可以通过SetConfigFile来指定文件名词,然后显式调用ReadInConfig,将配置加载到viper实例中。通过viper实例,我们可以在任意代码段中通过viper的GetXXX方法拿到配置。
port: 8123
version: "v1.2.3"
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigFile("config.yaml") // 指定配置文件
viper.AddConfigPath("./conf/") // 指定查找配置文件的路径
err := viper.ReadInConfig() // 读取配置信息
if err != nil { // 读取配置信息失败
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
// 监控配置文件变化
viper.WatchConfig()
r := gin.Default()
// 访问/version的返回值会随配置文件的变化而变化
r.GET("/version", func(c *gin.Context) {
c.String(http.StatusOK, viper.GetString("version"))
})
if err := r.Run(
fmt.Sprintf(":%d", viper.GetInt("port"))); err != nil {
panic(err)
}
}
我更喜欢的使用方式
比起直接操作viper对象,我更喜欢jassue中的封装方式,这里将配置信息封装给配置对象,在使用时直接操作配置对象。当然,这样的方式也有缺点,这里失去了配置的热修改能力。
type Application struct {
ConfigViper *viper.Viper
Config config.Configuration
Log *zap.Logger
DB *gorm.DB
Redis *redis.Client
Cron *cron.Cron
}
func InitializeConfig() *viper.Viper {
// 设置配置文件路径
config := "config.yaml"
if configEnv := os.Getenv("VIPER_CONFIG"); configEnv != "" {
config = configEnv
}
// 初始化 viper
v := viper.New()
v.SetConfigFile(config)
v.SetConfigType("yaml")
if err := v.ReadInConfig(); err != nil {
panic(fmt.Errorf("read config failed: %s \n", err))
}
// 监听配置文件
v.WatchConfig()
v.OnConfigChange(func(in fsnotify.Event) {
fmt.Println("config file changed:", in.Name)
// 重载配置
if err := v.Unmarshal(&global.App.Config); err != nil {
fmt.Println(err)
}
})
// 将配置赋值给全局变量
if err := v.Unmarshal(&global.App.Config); err != nil {
fmt.Println(err)
}
return v
}
待解决的疑问
- 怎样利用
viper进行配置项的初始化? viper是以什么样的方式作用在项目中的?- 我能从它的源码中学到什么?
Q1:怎样利用viper进行配置项的初始化?
1. 显式设置
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
2. 命令行
我感觉这个法子不好用,所以我直接不学这个。
3. 环境变量
Viper完全支持环境变量。这使Twelve-Factor App开箱即用。有五种方法可以帮助与ENV协作:
AutomaticEnv()BindEnv(string...) : errorSetEnvPrefix(string)SetEnvKeyReplacer(string...) *strings.ReplacerAllowEmptyEnv(bool)
使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写。
SetEnvPrefix("spf") // 将自动转为大写
BindEnv("id")
os.Setenv("SPF_ID", "13") // 通常是在应用程序之外完成的
id := Get("id") // 13
4. 配置文件
viper.SetConfigFile("./config.yaml") // 指定配置文件路径
viper.SetConfigName("config") // 配置文件名称(无扩展名)
viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项
viper.AddConfigPath("/etc/appname/") // 查找配置文件所在的路径
viper.AddConfigPath("$HOME/.appname") // 多次调用以添加多个搜索路径
viper.AddConfigPath(".") // 还可以在工作目录中查找配置
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件的错误
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
5. 远程Key/Value
在Viper中启用远程支持,需要在代码中匿名导入viper/remote这个包。Viper预先定义了许多配置源,如文件、环境变量、标志和远程K/V存储,但我不受其约束。我还可以实现自己所需的配置源并将其提供给viper。
viper.SetConfigType("yaml") // 或者 viper.SetConfigType("YAML")
// 任何需要将此配置添加到程序中的方法。
var yamlExample = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
jacket: leather
trousers: denim
age: 35
eyes : brown
beard: true
`)
viper.ReadConfig(bytes.NewBuffer(yamlExample))
viper.Get("name") // 这里会得到 "steve"
6. 默认值
我认为viper的默认值配置方式和显式配置并没有什么本质区别,只是起标记作用,将显式配置降级使用。
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
Q2:viper是以什么样的方式作用在项目中的?
从面向对象的角度来说,viper以Viper对象的形式存在于golang程序中,通常以变量指针的形式关联于全局变量中。通过github.com/jassue这个框架项目,我学习到这一点。jassue/global包下,可以直观的看到这一点:
type Application struct {
ConfigViper *viper.Viper
Config config.Configuration
Log *zap.Logger
DB *gorm.DB
Redis *redis.Client
Cron *cron.Cron
}
从功能角度来看,viper是一种内存键值对工具,并具备优先级和热修改能力。
Q3:我能从它的源码中学到什么?
viper实现了一个并不复杂的功能,但是它很实用,这样的案例的学习难度较低,同时对于写一些小项目很有帮助。viper的项目文件采用平铺式目录结构,源码文件注释完备,对于存在分支的位置进行了更详细的描述。
我学到的技巧
- 单分支语句处,同行注释
- 多分支语句处,最上方注释,且内部分支不再注释
- 新增的功能,以新模块的方式加入原始项目,如:
remote、internal
思考与总结
viper适用于小型项目的配置管理,它是否同样适用于构建分布式项目的配置同步。