配置工具的选择

通常,在一个或多个项目中会有多种格式的配置文件,比如PHP的php.ini文件、Nginx的server.conf文件,那么使用Golang怎么去读取这些不同格式的配置文件呢?
比如常见的有 JSON文件、INI文件、YAML文件和TOML文件等等。其中这些文件,对应的Golang处理库如下:

  • encoding/json – 标准库中的包,可以处理JSON配置文件,缺点是不能加注释
  • gcfg&goconfig – 处理INI配置文件
  • toml – 处理TOML配置文件
  • viper – 处理JSON, TOML, YAML, HCL以及Java properties配置文件

通常情况下,推荐使用viper库来读取配置文件,虽然它不支持ini格式的配置文件,但我们可以使用goconfig 或gcfg.v1库读取ini 格式配置文件。
viper 支持以下功能:

  • 支持Yaml、Json、 TOML、HCL 等格式的配置文件
  • 可以从文件、 io.Reader 、环境变量、cli命令行读取配置
  • 支持自动转换的类型解析
  • 可以远程从Key/Value中读取配置,需要导入 viper/remote 包
  • 监听配置文件。以往我们修改配置文件后需要重启服务生效,而Viper使用watch函数可以让配置自动生效。

    安装viper

    1. go get github.com/spf13/viper
    2. go get github.com/fsnotify/fsnotify

使用viper读取JSON配置文件

假设现在有一份 json 格式的配置文件 config.json

  1. {
  2. "date": "2019-04-30",
  3. "mysql": {
  4. "url": "127.0.0.1:3306",
  5. "username": "root",
  6. "password": "123456"
  7. }
  8. }

读取json配置文件

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/spf13/viper"
  5. "os"
  6. )
  7. func main() {
  8. viper.SetConfigName("config") //设置配置文件的名字
  9. viper.AddConfigPath(".") //添加配置文件所在的路径
  10. viper.SetConfigType("json") //设置配置文件类型,可选
  11. err := viper.ReadInConfig()
  12. if err != nil {
  13. fmt.Printf("config file error: %s\n", err)
  14. os.Exit(1)
  15. }
  16. urlValue := viper.Get("mysql.url")
  17. fmt.Println("mysql url:", urlValue)
  18. fmt.Printf("mysql url: %s\n mysql username: %s\n mysql password: %s", viper.Get("mysql.url"), viper.Get("mysql.username"), viper.Get("mysql.password"))
  19. }

运行程序,查看效果

  1. $ go run viper_json.go
  2. mysql url: 127.0.0.1:3306
  3. mysql url: 127.0.0.1:3306
  4. mysql username: root
  5. mysql password: 123456

使用viper读取yaml配置文件

假设现在有一份yaml格式的配置文件 config_yaml.yaml

  1. port: 10666
  2. mysql:
  3. url: "127.0.0.1:3306"
  4. username: root
  5. password: 123456

读取yaml配置文件

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/spf13/viper"
  5. "github.com/fsnotify/fsnotify"
  6. "os"
  7. )
  8. func main() {
  9. viper.SetConfigName("config_yaml") //把json文件换成yaml文件,只需要配置文件名 (不带后缀)即可
  10. viper.AddConfigPath(".") //添加配置文件所在的路径
  11. //viper.SetConfigType("json") //设置配置文件类型
  12. err := viper.ReadInConfig()
  13. if err != nil {
  14. fmt.Printf("config file error: %s\n", err)
  15. os.Exit(1)
  16. }
  17. viper.WatchConfig() //监听配置变化
  18. viper.OnConfigChange(func(e fsnotify.Event) {
  19. fmt.Println("配置发生变更:", e.Name)
  20. })
  21. urlValue := viper.Get("mysql.url")
  22. fmt.Println("mysql url:", urlValue)
  23. fmt.Printf("mysql url: %s\nmysql username: %s\nmysql password: %s", viper.Get("mysql.url"), viper.Get("mysql.username"), viper.GetString("mysql.password"))
  24. }

viper其他重要功能

获取子级配置
当配置层级关系较多的时候,有时候我们需要直接获取某个子级的所有配置,可以这样操作:

  1. app:
  2. cache1:
  3. max-items: 100
  4. item-size: 64
  5. cache2:
  6. max-items: 200
  7. item-size: 80

如果要读取cache1下的max-items,只需要执行viper.Get(“app.cache1.max-items”)就可以了。
解析配置
可以将配置绑定到某个结构体、map上,有两个方法可以做到这一点:

  1. Unmarshal(rawVal interface{}) : error
  2. UnmarshalKey(key string, rawVal interface{}) : error
  3. var config Config
  4. var mysql MySQL
  5. err := Unmarshal(&config) // 将配置解析到 config 变量
  6. if err != nil {
  7. t.Fatalf("unable to decode into struct, %v", err)
  8. }
  9. err := UnmarshalKey("mysql", &mysql) // 将配置解析到 mysql 变量
  10. if err != nil {
  11. t.Fatalf("unable to decode into struct, %v", err)
  12. }

获取值
在Viper中,有一些根据值的类型获取值的方法,存在以下方法:

  1. Get(key string) : interface{}
  2. GetBool(key string) : bool
  3. GetFloat64(key string) : float64
  4. GetInt(key string) : int
  5. GetString(key string) : string
  6. GetStringMap(key string) : map[string]interface{}
  7. GetStringMapString(key string) : map[string]string
  8. GetStringSlice(key string) : []string
  9. GetTime(key string) : time.Time
  10. GetDuration(key string) : time.Duration
  11. IsSet(key string) : bool

如果 Get 函数未找到值,则返回对应类型的一个零值。可以通过 IsSet() 方法来检测一个健是否存在。

  1. viper.GetString("logfile")
  2. if viper.GetBool("verbose") {
  3. fmt.Println("verbose enabled")
  4. }

修改对应的配置

  1. viper.Set("Verbose", true)
  2. viper.Set("LogFile", LogFile)

使用goconfig读取ini配置文件

安装goconfig

  1. go get github.com/Unknwon/goconfig

假设database.conf配置文件,如下所示

  1. [mysql]
  2. username=root
  3. password=123456
  4. url=127.0.0.1:3306
  5. [redis]
  6. address=127.0.0.1:6379

使用goconfig读取ini格式配置文件

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/Unknwon/goconfig"
  5. "os"
  6. )
  7. var cfg *goconfig.ConfigFile
  8. func init() {
  9. config, err := goconfig.LoadConfigFile("database.conf") //加载配置文件
  10. if err != nil {
  11. fmt.Println("get config file error")
  12. os.Exit(-1)
  13. }
  14. cfg = config
  15. }
  16. func GlobalConfig() {
  17. glob, _ := cfg.GetSection("mysql") //读取全部mysql配置
  18. fmt.Println(glob)
  19. }
  20. func main() {
  21. password, _ := cfg.GetValue("mysql", "password") //读取单个值
  22. fmt.Println(password)
  23. username, _ := cfg.GetValue("mysql", "username") //读取单个值
  24. fmt.Println(username)
  25. err := cfg.Reload() //重载配置
  26. if err != nil {
  27. fmt.Printf("reload config file error: %s", err)
  28. }
  29. GlobalConfig()
  30. }

加载完全局配置后,该配置长驻内存,需要动态加载的话,使用cfg.Reload()方法。
运行程序,效果如下。

  1. $ go run goconfig.go
  2. 123456
  3. root
  4. map[password:123456 url:127.0.0.1:3306 username:root]