前言
不管是微服务,还是单体服务,配置的读取都是一个必不可少的部分。
在大部分应用程序中,大多数配置都是静态加载的,有时候可能还需要从多个源读取配置,从而使配置的读取变得更复杂。而在Go-Micro中,不管是动态读取配置、还是从多个源读取配置,都非常简单,唯一的难点就是没有文档支持。
Go-Micro支持多种源的读取,包括命令行参数、文件(json、yaml)、etcd、consul、k8s等。
核心概念
Go-Micro 配置模块的核心概念有四个,分别是 Source、Encoder、Reader、Config。
对于Source,它表示读取的配置源,比如文件、命令行、consul、etcd等。Go-Micro官方支持的源有:
- cli:即命令行参数
- env:从环境变量中读取
- consul:从consul中读取
- etcd:从etcd中读取
- file:从文件中读取
- flag:从flags中读取
- memory:从内存中读取
除了官方支持的源,还有一些是Go-Micro社区支持的:
- configmap:从 k8s 的configmap中读取
- grpc:从grpc服务器读取
- url:从URL读取
- runtimevar:read from Go Cloud Development Kit runtime variable
- vault:read from Vault server
对于Encoder,它表示的是如何解析配置,比如json文件和yaml文件同属文件源,但是使用的解析器是不同的。Go-Micro默认的解析器是json,除了json解析器外,官方支持的解析器还有 yaml、toml、xml、hcl。
对于Reader,它的作用是将多个源的配置合并为一个。因为对于json、yaml、或者是consul,其配置结构都是类似的,最终Go-Micro都会将其转换成map[string]interface{}
形式,并进行合并。
对于Config,它扮演的是一种管理者的角色,它负责配置的新建、配置的读取、配置的同步、配置的监听等功能。
配置读取实战
文件准备
# ./file/config.json
{
"hosts": {
"database": {
"address": "10.0.0.1",
"port": 3306
},
"cache": {
"address": "10.0.0.2",
"port": 6379
}
}
}
# ./file/config.yaml
hosts:
database:
address: 10.0.0.3
port: 3307
cache:
address: 10.0.0.4
port: 6378
注:两份文件的结构是一样的,只不过用不同形式展示,且配置内容有所区别,主要是方便后面演示。
json读取
1.方式一:使用LoadFile读取(本质是对Load函数的封装)
import (
"encoding/json"
"fmt"
"github.com/micro/go-micro/v2/config"
"github.com/micro/go-micro/v2/config/source/file"
)
func readJson() {
// 加载配置文件
err := config.LoadFile("./file/config.json")
if err != nil {
panic(err)
}
// data的格式是 map[string]interface{}
// data map[hosts:map[cache:map[address:10.0.0.2 port:6379] database:map[address:10.0.0.1 port:3306]]]
data := config.Map()
fmt.Println("data", data)
}
2.方式二:新建文件源,并使用Load读取
func readJsonBySource() {
// 使用的是默认的配置实例:DefaultConfig
fileSource := file.NewSource(file.WithPath("./file/config.json"))
err := config.Load(fileSource)
if err != nil {
panic(err)
}
data := config.Map()
fmt.Println("data", data)
}
3.方式三:新建配置实例,使用Load读取
func readJsonByCustom() {
// 自定义的配置实例
myConf, err := config.NewConfig()
if err != nil {
panic(err)
}
err = myConf.Load(file.NewSource(file.WithPath("./file/config.json")))
if err != nil {
panic(err)
}
data := myConf.Map()
fmt.Println("data", data)
}
4.方式四:读取配置,将数据解析为结构体
type Host struct {
Address string `json:"address"`
Port int `json:"port"`
}
type Config struct {
Hosts map[string]Host `json:"hosts"`
}
func readJsonByStruct() {
err := config.LoadFile("./file/config.json")
if err != nil {
panic(err)
}
conf := Config{}
// 注意传的是指针
err = config.Scan(&conf)
if err != nil {
panic(err)
}
fmt.Println("data", conf)
}
5.方式五,读取配置,得到二进制数据,自己解析 (这种方式需要新建一个源)
func readJsonByStruct2() {
// 新建一个源
fileSource := file.NewSource(file.WithPath("./file/config.json"))
err := config.Load(fileSource)
if err != nil {
panic(err)
}
// 官方文档中提到,从source读取配置后会以 changeSet 的格式返回
changeSet, err := fileSource.Read()
if err != nil {
panic(err)
}
conf := Config{}
// 自己解析二进制数据
err = json.Unmarshal(changeSet.Data, &conf)
if err != nil {
panic(err)
}
fmt.Println("data", conf)
}
上面一共使用了五种方法去解析json,对于其他数据源,其实过程都是差不多的,大体流程就是:
- 1.新建一个源,比如 file.NewSource,并传入对应的配置,比如文件路径等
- 2.读取源里面的数据,使用
config.Load
方法,这时数据会存到默认的配置实例中 - 3.从配置实例里读取数据,有 Map()、Read()、Scan() 等方法可以选择
yaml读取
yaml读取其实和json差不多,唯一区别的是需要定义一个yaml解析器,不然用json解析器解析肯定报错。
import (
"fmt"
"github.com/micro/go-micro/v2/config"
"github.com/micro/go-micro/v2/config/encoder/yaml"
"github.com/micro/go-micro/v2/config/source"
"github.com/micro/go-micro/v2/config/source/file"
)
func readFromYaml() {
// 定义yaml解析器
enc := yaml.NewEncoder()
fileSource := file.NewSource(file.WithPath("./file/config.yaml"), source.WithEncoder(enc))
err := config.Load(fileSource)
if err != nil {
panic(err)
}
fmt.Println("data", config.Map())
}
多数据源读取
前面提到过,json配置和yaml配置其实是存在一点区别,如端口号等,目的是为了演示Go-Micro多数据源读取的场景。
既然是多数据源读取,就有优先级之分,而且优先级高的会覆盖优先级低的配置,这也是大部分应用程序的常见手段,先有一份基底配置,然后不断往上更新配置。
import (
"fmt"
"github.com/micro/go-micro/v2/config"
"github.com/micro/go-micro/v2/config/encoder/yaml"
"github.com/micro/go-micro/v2/config/source"
"github.com/micro/go-micro/v2/config/source/file"
)
func main() {
jsonSource := file.NewSource(file.WithPath("./file/config.json"))
yamlEncoder := yaml.NewEncoder()
yamlSource := file.NewSource(file.WithPath("./file/config.yaml"), source.WithEncoder(yamlEncoder))
// 后面读取的优先级越高,所以yaml的配置会覆盖json的配置
err := config.Load(jsonSource, yamlSource)
if err != nil {
panic(err)
}
// data map[hosts:map[cache:map[address:10.0.0.4 port:6378] database:map[address:10.0.0.3 port:3307]]]
fmt.Println("data", config.Map())
}
etcd 读取
关于 etcd 的安装配置这里就不赘述了,为了能够读取到数据,我们先要往etcd写点数据
# /micro/etcd 是我们的key
$ etcdctl put /micro/etcd '{"hosts":{"database":{"address":"10.0.0.1","port":3306},"cache":{"address":"10.0.0.2","port":6379}}}'
使用Go-Micro读取:
package main
import (
"fmt"
"github.com/micro/go-micro/v2/config"
"github.com/micro/go-micro/v2/config/source/etcd"
)
func main() {
etcdSource := etcd.NewSource(
etcd.WithAddress("127.0.0.1:2379"),
etcd.WithPrefix("/micro/etcd"),
etcd.StripPrefix(true),
)
err := config.Load(etcdSource)
if err != nil {
panic(err)
}
conf := Config{}
err = config.Scan(&conf)
fmt.Println("data", conf)
}
大家可以发现,其实etcd配置的读取和文件的读取方式差不多,这也是Go-Micro的巧妙之处,通过高度抽象的方式,简化了不同数据源的读取方式。
另外,可能有小伙伴对 StripPrefix
这个配置存在疑惑,不过通过下面这个例子大家应该就看得懂了
func main() {
etcdSource := etcd.NewSource(
etcd.WithAddress("127.0.0.1:2379"),
etcd.WithPrefix("/micro/etcd"),
etcd.StripPrefix(false),
)
err := config.Load(etcdSource)
if err != nil {
panic(err)
}
changeSet, err := etcdSource.Read()
if err != nil {
panic(err)
}
// StripPrefix 从语义上看是去掉前缀的意思,如果没有去掉前缀,则会保留micro、etcd这两个key
// {"micro":{"etcd":{"hosts":{"cache":{"address":"10.0.0.2","port":6379},"database":{"address":"10.0.0.1","port":3306}}}}}
fmt.Println(string(changeSet.Data))
conf := Config{}
// Get的作用相当于 conf["micro"]["etcd"]
// 所以 Get("micro", "etcd") 之后的数据为 {"hosts":{"cache":{"address":"10.0.0.2","port":6379},"database":{"address":"10.0.0.1","port":3306}}}
err = config.Get("micro", "etcd").Scan(&conf)
if err != nil {
panic(err)
}
fmt.Println("data", conf)
}
总结
关于Go-Micro配置模块的介绍就到这里了,希望能对你们有所帮助,另外,这里只是讲解了一些个人认为比较重要的点,如果需要更复杂的,可以参考下官方文档