kitex icon indicating copy to clipboard operation
kitex copied to clipboard

Proposal: Kitex 对接远程配置中心的方案

Open SinnerA opened this issue 3 years ago • 1 comments

目标

多数据源

定义标准化的接口来适配不同的数据源。 数据源可以是环境变量,本地文件,这两个由框架内置,也可以是常见的配置中心,例如 etcd、nacos 和 apollo 等,会由 kitex-contrib 来提供。 如果有特殊需求,可以通过接口实现来适配。 支持一次性从多个数据源加载数据,key 相同的配置将会被覆盖,具体的优先级由配置源实现中的读取顺序来决定。

多格式

支持多种格式的数据解析,内置常见的 json 和 yaml,如果用户有需要可以实现对应的 Decoder 接口。

动态加载

在应用启动阶段会首次加载所需配置,并在后台监听配置的变更,自动重新加载最新配置,服务不需要重启便能更新配置。

可观察性

能够观察到当前正在使用的配置,以及配置的每次变更事件,以便排查问题。

接口定义

// Loader is the entry of configuration.
type Loader interface {
   Load() Config
}

// Parser parse the raw data from configCenter to readable config.
type Parser interface {
   Parse(data []byte) (map[string]interface{}, error)
   String() string
}

// Config is configuration been stored in dynamic config center.
type Config interface {
   Equal(Config) bool
}

// Provider provide the raw data of config.
type Provider interface {
   Load() ([]byte, error)
   Watch() Watcher
}

// EventHandler define all event handlers about config change.
type EventHandler interface {
   OnAdd(key string, val interface{})
   OnUpdate(key string, oldVal, newVal interface{})
   OnDelete(key string)
}

// Watcher watch and handle the change events.
type Watcher interface {
   Watch(e EventHandler)
}

// Dumper dump cached data.
type Dumper interface {
   Dump() interface{}
}
  • Loader:它通过一个 key 来获取配置 Config。它是 Provider、Parser 的使用者,通过 Provider 获取到配置的 raw data,然后通过 Parser 完成配置内容的解析。Loader 还通过 Provider 返回的 Watcher 来获取配置变更相关的通知事件,并重新加载配置 Config;
  • Provider:它是配置的提供者,可以是本地文件,也可以远程的配置中心,如 k8s configmap,etcd等。Provider 能够感知到配置的变更,并给 Loader 发出通知,所以 Provider 需要返回一个 Watcher 给 Loader;
  • Watcher:配置信息是在初次加载后,当发生变化时希望框架能感知到并使用加载最新配置。Watcher 的作用就是监听配置的变更事件并及时通知框架,如本地文件的话可以通过定时检查文件的 mtime 是否发生变化来确认变更;
  • EventHandler:Watcher 的所有事件 handler,每种类型的事件都有对应的 handler 来处理;
  • Parser:Loader 拿到配置数据后,还需要通过合适的解析器将数据解析成某种结构化的配置信息,如JSON、YAML等;
  • Config:不管是哪种类型的配置信息,都应该是可对比的,因为很多时候需要靠对比来判断是否有变更;
  • Dumper:用于把缓存中的配置 dump 下来,类似于 snapshot 用于观察当前正在使用的配置;

如何使用

初始化

从本地文件加载

import (
    "github.com/cloudwego/kitex/config"
    "github.com/cloudwego/kitex/config/file"
)

path := "conf/config.yaml"
c := config.NewProvider(
    config.WithSource(
        file.NewSource(path),
    )
)

从外部配置中心加载,例如 consul

import (
    "github.com/cloudwego/kitex-contrib/consul"
)

consulClient, err := api.NewClient(&api.Config{
  Address: "127.0.0.1:8500",
})
if err != nil {
   panic(err)
}

cs, err := consul.New(consulClient, consul.WithPath("app/kitex/configs/"))
if err != nil {
  panic(err)
}
c := config.NewProvider(config.WithSource(cs))

不同的配置源使用方式大致相同,需要参考各自的文档或 example。

读取配置

在初始化的时候,需要指定 Parser 用于将配置内容反序列化解析出来

import (
    "gopkg.in/yaml.v2"
    "github.com/cloudwego/kitex/config/parser/yaml"
)

path := "conf/config.yaml"
c := config.NewProvider(
  config.WithSource(file.NewSource(path)),
  config.WithParser(yaml.NewParser()),
)

Provider 通过 Load 拿到 Config,再通过 Parser 会返回一个 map,通过 key 来获取对应的 value

监听变更

Provider 里面的 Watch 方法会返回一个 Watcher,会监听某个 key 的 Add、Update 和 Delete 事件,在本地或配置中心的配置有变更时,执行自定义的回调函数进行处理。

数据快照

提供了 Dump 接口,给 debug 端口使用,可以随时观察到当前缓存中的数据快照,方便排查问题

框架集成

Config 有两种使用方,一个是框架自身用于获取服务治理相关的配置,例如超时设置和熔断等,另一个则是用户用来获取一些业务相关的配置。 为了只维护一份缓存,框架会提供一个 configManager,configManager 里面会管理所有的 cofing。 由于服务治理的配置需要在服务启动前或发起调用前就需要获取,然而框架并不知道需要加载哪些类型的配置,因此需要用户实现 fetchFunc,用于 configManager 能够及时加载相关配置。

// ConfigurationManager provides access to get config, and manages configurator's lifecycle.
type ConfigurationManager interface {
   Get(key, namespace string) interface{}
}

// ServiceMeta describe metas about service.
type ServiceMeta interface {
   Name() string
   Type() ServiceType
   Tags() map[string]string // cluster, method, etc
}

// ManagerOption set values of ManagerOptions.
type ManagerOption func(*ManagerOptions)

// ManagerOptions contains settings of ConfigurationManager.
type ManagerOptions struct {
   // serviceHandler register callers and callees, it's used to obtain the configuration in advanced.
   serviceHandler func(callers, callees serviceMeta ServiceMeta) error
   // fetchFunc define the func which fetch remote configuration from dynamic config center.
   fetchFunc func(configLoader Loader, callers, callees []ServiceMeta) (map[string]Config, error)
}

FAQ

  1. Q:如何区分框架内服务治理的配置和用户自定义配置? A:通过类似 namespace 或 group 的概念来区分,主流的配置中心都有对应的概念。框架内置的服务治理配置 namespace 约定为 kitex_remote_config。
  2. Q:如何扩展对接不同的配置中心? A:在 kitex-contrib 中提供对应实现,例如 etcd、consul 等
  3. Q:给个具体的使用 example A:后续会在 kitex-example 中提供

SinnerA avatar Jul 25 '22 06:07 SinnerA

  • 治理配置约定的 Key 有哪些需要明确
  • 不同配置源的优先级需要明确
  • 业务配置和治理配置如果进行区分,可以给定具体的前缀,也要参考下后续 hertz 的情况

GuangmingLuo avatar Jul 26 '22 08:07 GuangmingLuo

治理配置的加载在 #973 中跟进。

felix021 avatar Oct 30 '23 12:10 felix021