这次我想分享如何管理分布式应用系统中的配置项,我们的实践与遇到的问题
为什么要用配置中心
我相信大家在开发的过程中,经常会遇到的问题是
- 我有个新的特性,但是我需要对其进行控制,在必要时能将功能关闭或打开
- 链接数据库的线程池需要调整
- 访问某个目标的超时时间需要调整
- 调节任务执行器的线程池大小
- 随着业务发展,几百,几千的虚机或是容器实例配置需要管理
难道每次配置我都要去代码库中改变然后重新发布版本么?我在曾经的项目中就是这么做的,得益于当时完善的CICD,我们只需要几分钟就可以完成这样的一次变更,但是依然要面临进程的重启,所以之后的实践中我们直接将配置管理放入到了编程框架中,并配以中心式的配置管理服务
使用编程框架
我在这篇分享中有大量分享欢迎参考,Go语言分布式系统配置管理实践--go archaius
有了这次改变后我们就做到了在运行时让配置生效,不用通过流水线重新发布新版本。
在流水线中能做的只是在发布一个服务时,对接配置中心的API,对相关配置进行变更。。比如进行一次新版本的发布,更改金丝雀发布策略。
配置逐渐变得难以管理
配置中心的数据模型逐渐无法满足需求
Dimension概念
配置项以Dimension划分 Dimension由{service}@{app}#{version}表示,也就是服务相关信息拼接而成的字符串。
Dimension就是一个唯一的ID,关联各个配置项,也就是一段json结构体
{ "timout":"1s", "pool_size":"10", }
也就是说我们对着微服务,版本,所属应用3个维度来下发信息
如果要更改微服务级别的服务,即不区分版本,我们就对{service}@{app}这个dimension进行变更。
能力就仅限于这两个层级了。
随着业务的发展我们发现这样的结构体不能满足一些场景
- 配置中心围绕微服务的治理概念进行设计(超时,熔断,负载均衡策略等配置),但是用户的业务系统配置也使用这套配置中心和开发框架管理(比如我说的某个功能开关),不该让用户去理解微服务,版本,app的概念,而是直白的,让用户更改配置,比如按照环境去下发environment=test或者production
- 服务的元数据不只是version,app,可能还有instance,也就是对着服务进程,只改变某个进程的配置进行小范围测试,除了instance还有更多元数据需要考虑,我们无法做到精准控制,比如按照project
key变得复杂
由于json结构体的局限性配置项的key越来越不易理解,key越来越长,比如
{ "ServiceB.timeout":"1s", "ServiceB.user.getUser.timeout":"10", }
第一行表示访问服务B时超时是多少
第二行表示到服务B的getUser API超时是多少
这还远远不够,服务还有版本,环境,所属app等多个字段,如下配置,由于每个字段没有语义,我们已经无法知道第二个字段到底是什么意思了。
{ "ServiceB.v1.timeout":"1s", "ServiceB.user.getUser.timeout":"10", }
难道我们都要继续拼接下去?我们来看看问题
- key语义学习成本高,语义容易混乱
- 所有的key集中在一个列表中管理,管理成本高。
- 当拼接规则复杂起来,人类不可读。
- Key设计几乎无法扩展或者变更(需要极高成本才能变更)
当然我们可以给json的value传入个json,yaml,java properties值,key用更大的维度来表示,比如文件名,但是一个围绕微服务概念设计的配置中心做不到通用,Dimension需要更加灵活,让人易于理解。
Apache ServiceComb Kie
在发展的过程中,我们重新设计了配置中心,不只围绕微服务场景,或者像kubernetes的config map围绕容器,而是希望能够成为一个通用的配置中心,让更多的生态接入。
项目地址:https://github.com/apache/servicecomb-kie
重新设计的结构体。
kie取自key的谐音
正如名字所突出的,key成为了同等公民,一个文件名"xxx,yaml",一个传统的key“timeout” 都可以是一个key,我们希望key简单,易理解。然后围绕key定义labels以表示复杂的语义
直接演示下运行效果:
key就在API path中(timeout),label与value在request body中定义
这句话的意思是,在对订单服务进行访问时,超时是1s
返回的respsonse body中,可以看到版本号“revision”是2,这说明,label是第二次被使用(之间labels的历史记录已经被储存了)
value具备type,不传就是plain text,在前台显示是可以根据类型做展示,更易读
最重要的是labels,你可以为key定义多个labels,只有label完全一致,才认为这是个唯一的“Dimension”,我用label概念来代替了过去定死的Dimension。有了labels与key,你已经可以*的去定义自己的配置,表达自己的语义
我接着新建了一个配置,表示访问该服务,要使用会话粘纸,但是可以看到,revison是3,因为历史管理是根据label记录的,以让用户可以根据labels来将所有key快速回滚到某个历史状态中,单一的回滚key来试图恢复系统也许会引起更大的混乱。
我们再来个稍微复杂的
这次我们新建了一个复杂语义的label,表示:如果前台访问订单服务的1.0.1,那么负载均衡策略是round robin。
今天的介绍就简单到这里,后面会持续对kie的新特性进行分享,通过这篇我希望通过分享让大家对复杂分布式系统中的配置管理实践有些了解。