micro中的动态配置

656 阅读6分钟

应用程序中的大多数配置都是静态配置的,或者包含从多个源加载的复杂逻辑。Go-config使此操作变得容易,可插入和可合并。 您再也不必用相同的方式处理config了。

特征:

  • 动态加载:根据需要从多个源加载配置。Go Config在后台管理监控配置源,并自动合并和更新内存视图。
  • 来源可插拔:从任意数量的源中进行选择以加载和合并配置。后端源被抽象为内部使用的标准格式,并通过编码器解码。源可以是env变量、flags、file、etcd、k8s configmap等。
  • 可以合并配置:如果您指定了多个配置源,则无论格式如何,它们都将合并并在单个视图中显示。这大大简化了优先顺序的加载和基于环境的更改。
  • 观察变化:可以选择查看配置中对特定值的更改。使用Go Config的监视器热重新加载你的应用程序。如果需要通知,只需要保持读取配置文件并监控它的变化
  • 安全恢复:万一配置加载不佳或由于某些未知原因而被彻底清除,您可以在直接访问任何配置值时指定回退值。这样可以确保您在出现问题时始终可以阅读默认内容。

主要分为以下几部分内容

  • Source: 加载配置的后端,可以同时使用多个源。
// Source 是从中加载配置的源
type Source interface {
	Read() (*ChangeSet, error)
	Write(*ChangeSet) error
	Watch() (Watcher, error)
	String() string
}

// ChangeSet 表示源中的一组更改
type ChangeSet struct {
	Data      []byte
	Checksum  string
	Format    string
	Source    string
	Timestamp time.Time
}

// Watcher 监视源的更改
type Watcher interface {
	Next() (*ChangeSet, error)
	Stop() error
}

  • Encoder: 处理编码/解码源配置,后端源可能以许多不同的格式存储配置。编码器使我们能够处理任何格式。如果未指定编码器,则默认为json。
type Encoder interface {
	Encode(interface{}) ([]byte, error)
	Decode([]byte, interface{}) error
	String() string
}

默认的编码器jsonEncoder

type jsonEncoder struct{}

func (j jsonEncoder) Encode(v interface{}) ([]byte, error) {
	return json.Marshal(v)
}

func (j jsonEncoder) Decode(d []byte, v interface{}) error {
	return json.Unmarshal(d, v)
}

func (j jsonEncoder) String() string {
	return "json"
}

func NewEncoder() encoder.Encoder {
	return jsonEncoder{}
}

  • Reader: 将多个编码源合并为一种格式

Reader将多个变更集表示为单个合并且可查询的值集。

type Reader interface {
	Merge(...*source.ChangeSet) (*source.ChangeSet, error)
	Values(*source.ChangeSet) (Values, error)
	String() string
}

reader利用编码器将changeset解码为map[string]interface{}然后将他们合并到一个单个的changeset.它查看“格式”字段以确定编码器.然后将变更集表示为一组Values,这些Values具有检索Go类型和无法加载值的时候进行回退的能力。


// Values reader的返回值
type Values interface {
	Bytes() []byte
	Get(path ...string) Value
	Set(val interface{}, path ...string)
	Del(path ...string)
	Map() map[string]interface{}
	Scan(v interface{}) error
}

Value接口允许强制类型转换/类型断言到go类型

// Value 代表一个任何类型的值
type Value interface {
	Bool(def bool) bool
	Int(def int) int
	String(def string) string
	Float64(def float64) float64
	Duration(def time.Duration) time.Duration
	StringSlice(def []string) []string
	StringMap(def map[string]string) map[string]string
	Scan(val interface{}) error
	Bytes() []byte
}

  • Config: 配置管理器,管理所有的config, 多个源,编码器以及读取器

它管理来自多个后端源的读取,同步和监视,并将它们表示为单个合并的可查询源。

// Config is an interface abstraction for dynamic configuration
type Config interface {
	// provide the reader.Values interface
	reader.Values
	// Init the config
	Init(opts ...Option) error
	// Options in the config
	Options() Options
	// Stop the config loader/watcher
	Close() error
	// Load config sources
	Load(source ...source.Source) error
	// Force a source changeset sync
	Sync() error
	// Watch a value for changes
	Watch(path ...string) (Watcher, error)
}
  • Secrets: 支持在配置中加载编码密码
// Secrets 加密和解密任意数据,数据应该尽可能的小。
type Secrets interface {
	// 初始化选项
	Init(...Option) error
	// Return the options
	Options() Options
	// Decrypt a value
	Decrypt([]byte, ...DecryptOption) ([]byte, error)
	// Encrypt a value
	Encrypt([]byte, ...EncryptOption) ([]byte, error)
	// Secrets implementation
	String() string
}
  • Usage: 使用go-config的案例

1.创建一个config

import "github.com/micro/go-micro/v2/config"

conf := config.NewConfig()
  1. 从一个源文件中加载配置,根据文件的扩展名来决定config的格式
import (
	"github.com/micro/go-micro/v2/config"
)

// Load json config file
config.LoadFile("/tmp/config.json")

3.读取Config

将整个config作为一个map读取

// retrieve map[string]interface{}
conf := config.Map()

// map[cache:map[address:10.0.0.2 port:6379] database:map[address:10.0.0.1 port:3306]]
fmt.Println(conf["hosts"])

将整个config 扫描到一个结构体中

type Host struct {
        Address string `json:"address"`
        Port int `json:"port"`
}

type Config struct{
	Hosts map[string]Host `json:"hosts"`
}

var conf Config

config.Scan(&conf)

// 10.0.0.1 3306
fmt.Println(conf.Hosts["database"].Address, conf.Hosts["database"].Port)

4.读取Values

从config扫描一个值到一个结构体中

type Host struct {
	Address string `json:"address"`
	Port int `json:"port"`
}

var host Host

config.Get("hosts", "database").Scan(&host)

// 10.0.0.1 3306
fmt.Println(host.Address, host.Port)

以Go类型读取单个值

// Get address. Set default to localhost as fallback
address := config.Get("hosts", "database", "address").String("localhost")

// Get port. Set default to 3000 as fallback
port := config.Get("hosts", "database", "port").Int(3000)

5.监控路径

观察路径的变化。当文件更改时,新值将可用。

w, err := config.Watch("hosts", "database")
if err != nil {
	// do something
}

// wait for next value
v, err := w.Next()
if err != nil {
	// do something
}

var host Host

v.Scan(&host)

6.多个源

可以加载和合并多个源。 合并优先级的顺序相反。

config.Load(
	// base config from env
	env.NewSource(),
	// override env with flags
	flag.NewSource(),
	// override flags with file
	file.NewSource(
		file.WithPath("/tmp/config.json"),
	),
)

7.设置源编码器

源需要编码器来编码/解码数据并指定changeset格式。 默认编码器是json。 要将编码器更改为yaml,xml,toml,请将他们作为选项指定。

e := yaml.NewEncoder()

s := consul.NewSource(
	source.WithEncoder(e),
)

8.添加Reader编码器 Reader使用编码器对来自不同格式源的数据进行解码。默认阅读器支持json,yaml,xml,toml和hcl.它将合并的配置表示为json。 通过将其指定为选项来添加新的编码器。

e := yaml.NewEncoder()

r := json.NewReader(
	reader.WithEncoder(e),
)
  • FAQ: 场景的问题以及答复

1.它跟Viper的有什么不同点?

Vipergo-config都是用来解决相同的问题的。go-config提供了一个不同的接口,并且它是micro生态系统的一个工具。

2.Encoder和Reader有什么不同的?

encoder是后端用于编码和解码数据的工具。reader使用编码器来解码多个源的不同格式的数据,然后合并到一个新的编码格式。

在file源的例子中,我们可以使用file的扩展名来决定config的格式,所以encoder并没有被使用

在consul/etcd或者相同类型的键值源的案例中,我们可能会从包含多个键的前缀加载,这意味着源需要了解编码,以便它可以返回单个changeset。

在环境变量和flags的案例中,我们也需要一个方法将值编码为字节,然后指定格式,然后后面reader可以把他们合并。

3.为什么是changeset数据不表示为map[stirng]interface{}

在某些情况下,源数据实际上可能不是键值,因此更容易将其表示为字节并延迟解码给阅读器。