Golang 语言怎么使用 Viper 管理配置信息?(下)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: Golang 语言怎么使用 Viper 管理配置信息?(下)

使用 Flags

Viper 能够绑定到 flags。具体来说,viper 支持 Cobra 库中使用的 Pflags。


与 BindEnv 一样,在调用绑定方法时,不会设置该值,而是在访问绑定方法时设置该值。这意味着您可以尽早绑定,即使在 init() 函数中。


对于单个 Flag,BindPFlag() 方法提供此功能。


例如:

serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))


您还可以绑定一组现有的 pflags(pflag.FlagSet):


例如:

pflag.Int("flagname", 1234, "help message for flagname")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
i := viper.GetInt("flagname") // retrieve values from viper instead of pflag


在 Viper 中使用 pflag 并不阻碍其他包中使用标准库中的 flag 包。pflag 包可以通过导入这些 flags 来处理为 flag 包定义的 flags。这是通过调用一个pflag 包提供的便利函数 AddGoFlagSet() 实现的。


例如:


package main
import (
  "flag"
  "github.com/spf13/pflag"
)
func main() {
  // using standard library "flag" package
  flag.Int("flagname", 1234, "help message for flagname")
  pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
  pflag.Parse()
  viper.BindPFlags(pflag.CommandLine)
  i := viper.GetInt("flagname") // retrieve value from viper
  ...
}


flag 接口

如果您不使用 Pflags,Viper 提供两个 Go 接口来绑定其他 flag 系统。


FlagValue 表示单个 flag。这是一个说明如何实现此接口的非常简单的示例:

type myFlag struct {}
func (f myFlag) HasChanged() bool { return false }
func (f myFlag) Name() string { return "my-flag-name" }
func (f myFlag) ValueString() string { return "my-flag-value" }
func (f myFlag) ValueType() string { return "string" }


一旦您的 flag 实现此接口,您只需告诉 Viper 将其绑定:


viper.BindFlagValue("my-flag-name", myFlag{})


FlagValueSet 表示一组 flags。这是一个说明如何实现此接口的非常简单的示例:


type myFlagSet struct {
  flags []myFlag
}
func (f myFlagSet) VisitAll(fn func(FlagValue)) {
  for _, flag := range flags {
    fn(flag)
  }
}


一旦您的 flag 集合实现此接口,您只需告诉 Viper 绑定它:

fSet := myFlagSet{
  flags: []myFlag{myFlag{}, myFlag{}},
}
viper.BindFlagValues("my-flags", fSet)


远程 Key/Value 存储支持

若要在 Viper 中启用远程支持,请对 viper/remote 包进行空白导入:


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


Viper 将读取从 Key/Value 存储(例如 etcd 或 Consul )中的路径检索到的配置字符串(如JSON,TOML,YAML,HCL 或 envfile)。这些值优先级高于默认值,但会被从磁盘,命令行参数(flag)或环境变量检索的配置值覆盖。


Viper 使用 crypt 从 K / V 存储中检索配置,这意味着如果您具有正确的 gpg 密钥,您可以将配置值加密后存储,并可以自动将其解密。加密是可选的。


您可以将远程配置与本地配置结合使用,也可以独立使用。


crypt 有一个命令行帮助程序,您可以用来将配置放入 K / V 存储中。crypt 默认使用在 http://127.0.0.1:4001 上的 etcd。


$ go get github.com/bketelsen/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json


确认已设置值:


$ crypt get -plaintext /config/hugo.json


有关如何设置加密值或如何使用 Consul 的示例,请参见 crypt 文档。

https://github.com/bketelsen/crypt


远程 Key/Value 存储示例 - 未加密

etcd


viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()


Consul

您需要使用具有所需配置的 JSON 值设置 Consul 存储中的 key。例如,创建一个具有 JSON 值得 Consul key/value 存储的 key MY_CONSUL_KEY。


{
    "port": 8080,
    "hostname": "myhostname.com"
}



viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // Need to explicitly set this to json
err := viper.ReadRemoteConfig()
fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // myhostname.com


Firestore



viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()

当然,您也可以使用 SecureRemoteProvider


远程 Key/Value 存储示例 - 加密


viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes,  supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()


监控 etcd 中的更改 - 未加密


// alternatively, you can create a new viper instance.
var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
// read from remote config the first time.
err := runtime_viper.ReadRemoteConfig()
// unmarshal config
runtime_viper.Unmarshal(&runtime_conf)
// open a goroutine to watch remote changes forever
go func(){
  for {
      time.Sleep(time.Second * 5) // delay after each request
      // currently, only tested with etcd support
      err := runtime_viper.WatchRemoteConfig()
      if err != nil {
          log.Errorf("unable to read remote config: %v", err)
          continue
      }
      // unmarshal new config into our runtime config struct. you can also use channel
      // to implement a signal to notify the system of the changes
      runtime_viper.Unmarshal(&runtime_conf)
  }
}()


04

怎么在 Viper 中获取配置项?


在 Viper 中,有几种根据值的类型获取值的方法。存在以下功能和方法:

  • Get(key string) : interface{}
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]interface{}
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]interface{}


认识到的一件重要事情是,每个 Get 函数如果不到值,它将返回零值。为了检查给定键是否存在,提供了 IsSet() 方法。


例如:


viper.GetString("logfile") // case-insensitive Setting & Getting
if viper.GetBool("verbose") {
    fmt.Println("verbose enabled")
}


访问嵌套键

访问器方法还接受深度嵌套键的格式化路径。例如,如果加载了以下JSON文件:


{
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}


Viper 可以通过传递「.」分隔键的路径来访问嵌套字段:


GetString("datastore.metric.host") // (returns "127.0.0.1")


遵守上面建立的优先级规则;搜索路径将遍历其余配置注册表,直到找到为止。


例如,在给定此配置文件的情况下,datastore.metric.host 和 datastore.metric.port 均已定义(并且可以被覆盖)。如果另外在默认设置中定义了 datastore.metric.protocol,Viper 也会找到它。


但是,如果 datastore.metric 被直接赋值覆盖(通过 flag,环境变量,Set() 方法等),则 datastore.metric 的所有子键也都变为未定义状态,它们被较高的优先级配置遮蔽(shadowed)了。


Viper 可以使用路径中的数字访问数组索引。例如:


{
    "host": {
        "address": "localhost",
        "ports": [
            5799,
            6029
        ]
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}
GetInt("host.ports.1") // returns 6029


最后,如果存在与分隔的键路径匹配的键,则将返回其值。例如


{
    "datastore.metric.host": "0.0.0.0",
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}
GetString("datastore.metric.host") // returns "0.0.0.0"

在开发可重用模块时,提取配置的子集并传递给模块通常很有用。这样,模块可以实例化一次,就获取到不同的配置。


例如,应用程序可能出于不同的目的使用多个不同的缓存存储:


cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80


我们可以将缓存名称传递给模块(例如 NewCache("缓存1"),但访问配置键需要奇怪的串联,并且与全局配置的分离更少。


因此,与其这样做,我们不要将 Viper 实例传递给表示配置子集的构造函数:


cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // Sub returns nil if the key cannot be found
    panic("cache configuration not found")
}
cache1 := NewCache(cache1Config)


注意:始终检查 Sub 的返回值。如果找不到 Key,则返回 nil。


在内部,NewCache 函数可以直接处理 max-items 和 item-size 的键:


func NewCache(v *Viper) *Cache {
    return &Cache{
        MaxItems: v.GetInt("max-items"),
        ItemSize: v.GetInt("item-size"),
    }
}


生成的代码易于测试,因为它与主配置结构分离,并且更易于重用(出于同样的原因)。


反序列化

您还可以选择将所有值或特定值解析到 struct、map 和 etc。


有两种方法可以做到这一点:

  • Unmarshal(rawVal interface{}) : error
  • UnmarshalKey(key string, rawVal interface{}) : error


例如:


type config struct {
  Port int
  Name string
  PathMap string `mapstructure:"path_map"`
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
  t.Fatalf("unable to decode into struct, %v", err)
}


如果要解析 Key 本身包含「.」(默认键分隔符)的配置,必须更改分隔符:


v := viper.NewWithOptions(viper.KeyDelimiter("::"))
v.SetDefault("chart::values", map[string]interface{}{
    "ingress": map[string]interface{}{
        "annotations": map[string]interface{}{
            "traefik.frontend.rule.type":                 "PathPrefix",
            "traefik.ingress.kubernetes.io/ssl-redirect": "true",
        },
    },
})
type config struct {
  Chart struct{
        Values map[string]interface{}
    }
}
var C config
v.Unmarshal(&C)


Viper 还支持解析到嵌入结构体:


/*
Example config:
module:
    enabled: true
    token: 89h3f98hbwf987h3f98wenf89ehf
*/
type config struct {
  Module struct {
    Enabled bool
    moduleConfig `mapstructure:",squash"`
  }
}
// moduleConfig could be in a module specific package
type moduleConfig struct {
  Token string
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
  t.Fatalf("unable to decode into struct, %v", err)
}


Viper 在内部使用

github.com/mitchellh/mapstructure 解析值,默认情况下使用 mapstructure tag。


序列化为字符串

您可能需要将 viper 中保存的所有设置序列化到字符串中,而不是将它们写入文件。您可以将您最喜爱的格式的序列化程序与 AllSettings() 返回的配置一起使用。


import (
    yaml "gopkg.in/yaml.v2"
    // ...
)
func yamlStringSettings() string {
    c := viper.AllSettings()
    bs, err := yaml.Marshal(c)
    if err != nil {
        log.Fatalf("unable to marshal config to YAML: %v", err)
    }
    return string(bs)
}


05

使用单个 Viper 实例,还是使用多个 Viper 实例?


Viper 可以开箱即用。无需配置或初始化,就可以使用 Viper。由于大多数应用程序都希望使用单个中央存储库进行配置,因此 viper 包提供了此功能。它类似于单例模式。


在上面的所有示例中,他们都以单例模式风格演示了使用 Viper 的使用方法。


使用多个 Viper 实例

您还可以创建许多不同的 Viper 实例,供应用程序使用。每个都有其独特的配置和值集。每个都可以从不同的配置文件、Key/Value 存储等读取。Viper 包支持的所有函数都镜像为 Viper 上的方法。


例如:


x := viper.New()
y := viper.New()
x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")
//...


当使用多个 Viper 时,由用户管理不同的 Viper。


06

使用 Viper 读取配置文件的模拟示例


模拟示例的项目目录:


.
├── configs
│   └── config.yaml
├── go.mod
├── go.sum
└── main.go


配置文件:

configs/config.yaml


Server:
  RunMode: debug
  HttpPort: 8080
  ReadTimeout: 60
  WriteTimeout: 60


使用 Viper 读取配置文件中的内容,并解码到 struct 中:

main.go


type ServerSetting struct {
  RunMode      string
  HttpPort     string
  ReadTimeout  time.Duration
  WriteTimeout time.Duration
}
var server ServerSetting
func main() {
  // 实例化 viper 实例
  vp := viper.New()
  // 设置配置文件名称
  vp.SetConfigName("config")
  // 设置配置文件类型
  vp.SetConfigType("yaml")
  // 添加配置文件路径
  vp.AddConfigPath("configs/")
  // 读取配置文件内容
  err := vp.ReadInConfig()
  if err != nil {
    panic(fmt.Errorf("Fatal error config file: %s\n", err))
  }
  //  解码 key 到 struct
  err = vp.UnmarshalKey("Server", &server)
  if err != nil {
    panic(fmt.Errorf("unable to decode into struct, %v", err))
  }
  // 打印
  fmt.Printf("Server: %+v\n", server)
}


07

总结


本文是 Viper 开源库的 README 的中文翻译,文章内容介绍了什么是 Viper,Viper 包含哪些功能和 Viper 管理配置信息的不同方式的使用方法,以及不同方式之间的优先级顺序。翻译内容难免有不准确的地方,建议对照英文原稿阅读。文章结尾,还给出了一个使用 Viper 读取配置文件的模拟示例。截止发稿,Viper 的最新版本为 v1.7.1,并且作者目前正在收集使用反馈,为开发 v2.0 做准备。





目录
相关文章
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
111 4
Golang语言之管道channel快速入门篇
|
2月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
66 4
Golang语言文件操作快速入门篇
|
2月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
101 3
Golang语言之gRPC程序设计示例
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
86 4
|
14天前
|
存储 Cloud Native Shell
go库介绍:Golang中的Viper库
Viper 是 Golang 中的一个强大配置管理库,支持环境变量、命令行参数、远程配置等多种配置来源。本文详细介绍了 Viper 的核心特点、应用场景及使用方法,并通过示例展示了其强大功能。无论是简单的 CLI 工具还是复杂的分布式系统,Viper 都能提供优雅的配置管理方案。
|
2月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
50 4
Golang语言goroutine协程篇
|
2月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
53 3
Golang语言之Prometheus的日志模块使用案例
|
1月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
30 0
|
2月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
46 3
|
2月前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
59 3
Golang语言之函数(func)进阶篇