该文章内容发布已经超过一年,请注意检查文章中内容是否过时。
之前在 Apache/dubbo-go(以下简称 dubbo-go )社区中,有同学希望配置文件不仅可以放于本地,还可以放于配置管理中心里。那么,放在本地和配置管理中心究竟有哪些不一样呢?
放在本地,每次更新需要重启,配置文件管理困难,无法做到实时更新即刻生效。此外,本地文件还依赖人工版本控制,在微服务的场景下,大大的增加了运维的成本与难度。
而配置管理中心提供了统一的配置文件管理,支持文件更新、实时同步、统一版本控制、权限管理等功能。
基于以上几个背景,可以总结出以下目标
配置中心在 dubbo-go 中主要承担以下场景的职责:
就目前而言,dubbo-go 首要支持的是 Dubbo 中支持的开源配置中心,包括:
而考虑到某些公司内部有自身的研发的配置中心,又或者当前流行而 Dubbo 尚未支持的配置中心,如 etcd,我们的核心在于设计一套机制,允许我们,也包括用户,可以通过扩展接口新的实现,来快速接入不同的配置中心。
那在 dubbo-go 中究竟怎么实现呢?我们的答案是:基于动态的插件机制在启动时按需加载配置中心的不同实现。
实现该部分功能放置于一个独立的子项目中,见: https://github.com/apache/dubbo-go/tree/master/config_center
原逻辑为:启动时读取本地配置文件,将其加载进内存,通过配置文件中的配置读取注册中心的信息获取服务提供者,注册服务消费者。
有些读者会有点困惑,不是说好了使用配置中心的,为什么现在又要读取本地配置呢?答案就是,读取的这部分信息分成两部分:
在改造的时候,需要考虑以下的问题:
1、如何实现支持多个配置中心?如何实现按需加载?
通过抽象 DynamicConfiguration 让开发者可以快速支持多个配置中心。使用者导入指定的组件包后,在启动阶段将需要的组件加载进内存中,以便给程序按需调用,如下图绿色部分。
2、配置中心的配置加载阶段在什么时候?
应在读取配置文件阶段后,读取并解析本地配置文件中配置中心信息。初始化配置中心链接,读取 /dubbo/config/dubbo/dubbo.properties 与 /dubbo/config/dubbo/应用名/dubbo.properties ,并将其加载到内存之中覆盖原有配置,监听其变更,实时更新至内存,如下图蓝色部分:
使用者加载对应配置中心模块后,在初始化阶段加入各配置中心模块往其中注册其初始化类。
package extension
import (
"github.com/apache/dubbo-go/config_center"
)
var (
configCenterFactories = make(map[string]func() config_center.DynamicConfigurationFactory)
)
// SetConfigCenterFactory sets the DynamicConfigurationFactory with @name
func SetConfigCenterFactory(name string, v func() config_center.DynamicConfigurationFactory) {
configCenterFactories[name] = v
}
// GetConfigCenterFactory finds the DynamicConfigurationFactory with @name
func GetConfigCenterFactory(name string) config_center.DynamicConfigurationFactory {
if configCenterFactories[name] == nil {
panic("config center for " + name + " is not existing, make sure you have import the package.")
}
return configCenterFactories[name]()
}
整个动态配置中心的关键点就在 DynamicConfigurationFactory 上,其中通过解析内部自定义的 URL ,获取其协议类型,反射其参数,用于创建配置中心的链接。
package config_center
import (
"github.com/apache/dubbo-go/common"
)
// DynamicConfigurationFactory gets the DynamicConfiguration
type DynamicConfigurationFactory interface {
GetDynamicConfiguration(*common.URL) (DynamicConfiguration, error)
}
如:
配置文件中配置:
config_center:
protocol: zookeeper
address: 127.0.0.1:2181
namespace: test
dubbo-go 内部会解析为:
zookeeper://127.0.0.1:2181?namespace=test
在内部传递,用于初始化配置中心链接。
PS: 在 dubbo-go 中到处可见这种内部协议,透彻理解这个内部协议对阅读 dubbo-go 代码很有帮助。
该接口规定了各个配置中心需要实现的功能:
// DynamicConfiguration for modify listener and get properties file
type DynamicConfiguration interface {
Parser() parser.ConfigurationParser
SetParser(parser.ConfigurationParser)
AddListener(string, ConfigurationListener, ...Option)
RemoveListener(string, ConfigurationListener, ...Option)
// GetProperties get properties file
GetProperties(string, ...Option) (string, error)
// GetRule get Router rule properties file
GetRule(string, ...Option) (string, error)
// GetInternalProperty get value by key in Default properties file(dubbo.properties)
GetInternalProperty(string, ...Option) (string, error)
// PublishConfig will publish the config with the (key, group, value) pair
PublishConfig(string, string, string) error
// RemoveConfig will remove the config white the (key, group) pair
RemoveConfig(string, string) error
// GetConfigKeysByGroup will return all keys with the group
GetConfigKeysByGroup(group string) (*gxset.HashSet, error)
}
优先考虑与现有 Dubbo 设计兼容,从而降低使用者的学习成本,dubbo-admin 作为服务提供者实现应用级配置管理, dubbo-go 作为消费端实现配置下发管理功能。下面以 ZooKeeper 为例,对服务提供者与服务消费者进行整体流程分析。
dubbo-admin 配置管理中增加 global 配置,ZooKeeper 中会自动生成其对应配置节点,内容均为 dubbo-admin 中设置的配置。
上图展示了 dubbo.properties 文件在 ZooKeeper 和 Apollo 中的存储结构:
ZooKeeper
Apollo
ZooKeeper 与 Apollo 最大的不一样就在于 dubbo.properties 所在的节点。
以 Apollo 为例,简单的介绍,如何实现支持一个新的配置管理中心。
本例中使用的 Apollo Go Client 为:https://github.com/zouyx/agollo 。
PS: 如没找到,自己实现也是可以的哦。
因为每个配置管理中心的存储结构各有特点,导致 Dubbo 在使用外部配置管理中心时,存储配置节点的结构不一样。在 dubbo-configcenter 找到希望支持的配置管理中心,而本例中 Apollo 则在 ApolloDynamicConfiguration.java 。
注释中表明,Apollo namespace = governance (governance .properties) 用于治理规则,namespace = dubbo (dubbo.properties) 用于配置文件。
新建创建客户端方法,最好客户端保持为单例。
func newApolloConfiguration(url *common.URL) (*apolloConfiguration, error) {
c := &apolloConfiguration{
url: url,
}
configAddr := c.getAddressWithProtocolPrefix(url)
configCluster := url.GetParam(constant.CONFIG_CLUSTER_KEY, "")
appId := url.GetParam(constant.CONFIG_APP_ID_KEY, "")
namespaces := getProperties(url.GetParam(constant.CONFIG_NAMESPACE_KEY, cc.DEFAULT_GROUP))
c.appConf = &config.AppConfig{
AppID: appId,
Cluster: configCluster,
NamespaceName: namespaces,
IP: configAddr,
}
agollo.InitCustomConfig(func() (*config.AppConfig, error) {
return c.appConf, nil
})
return c, agollo.Start()
}
以下为必须实现的方法,以下方法用于获取配置中心配置。
可选择实现的方法,如不实现,则不能动态更新 dubbo-go 中配置信息。
而 Parser & SetParser 使用默认实现即可,默认为 Properties 转换器。
更多信息,参考:dubbo-go-apollo ,详情参考: https://github.com/apache/dubbo-go/tree/release-1.5/config_center/apollo
从上面的设计里面,也能大概猜到怎么使用了:
很显然,使用配置中心并不复杂,只需要把对应的依赖引入进来。在包初始化的时候,会创建出来对应的配置中心的实现。比如说加载 ZooKeeper 或者 Apollo 作为配置中心:
ZooKeeper
_ "github.com/apache/dubbo-go/config_center/zookeeper"
Apollo
_ "github.com/apache/dubbo-go/config_center/apollo"
当然仅仅加载还不够,比如说虽然我加载了 zookeeper,但是我还需要知道怎么连上这个配置中心,即前面提到的配置中心的元数据,这部分信息是需要在本地配置出来的。比如说:
ZooKeeper
config_center:
protocol: "zookeeper"
address: "127.0.0.1:2181"
Apollo
如果需要使用 Apollo 作为配置中心,请提前创建 namespace: dubbo.properties,用于配置管理。
config_center:
protocol: "apollo"
address: "127.0.0.1:8070"
app_id: test_app
cluster: dev
更加具体的实现,我就不详细论述,大家可以去看源码,欢迎大家持续关注,或者贡献代码。
整个配置中心的功能,麻雀虽小,但五脏俱全。目前并不算是十分完善,但是整个框架层面上来说,是走在了正确的路上。从扩展性来说,是比较便利。目前支持的配置中心还不够丰富,只有 ZooKeeper 与 Apollo ,支持的配置文件格式也只有 properties ,虽然能满足基本使用场景,距离完善还有还长远的路。
未来计划:
本文作者: 邹毅贤,Github ID @zouyx,开源爱好者,就职于 SheIn 供应链部门,负责供应链开放平台