极客时间已完结课程限时免费阅读

53 | 管理设计篇之“配置中心”

53 | 管理设计篇之“配置中心”-极客时间

53 | 管理设计篇之“配置中心”

讲述:杨超

时长14:15大小13.02M

你好,我是陈皓,网名左耳朵耗子。
我们知道,除了代码之外,软件还有一些配置信息,比如数据库的用户名和密码,还有一些我们不想写死在代码里的东西,像线程池大小、队列长度等运行参数,以及日志级别、算法策略等,还有一些是软件运行环境的参数,如 Java 的内存大小,应用启动的参数,包括操作系统的一些参数配置……
所有这些东西,我们都叫做软件配置。以前,我们把软件配置写在一个配置文件中,就像 Windows 下的 ini 文件,或是 Linux 下的 conf 文件。然而,在分布式系统下,这样的方式就变得非常不好管理,并容易出错。于是,为了便于管理,我们引入了一个集中式的配置管理系统,这就是配置中心的由来。
现在,软件的配置中心是分布式系统的一个必要组件。这个系统听起来很简单,但其实并不是。我见过好多公司的配置中心,但是我觉得做得都不好,所以,想写下这篇文章给你一些借鉴。

配置中心的设计

区分软件的配置

首先,我们要区分软件的配置,软件配置的区分有多种方式。
有一种方式是把软件的配置分成静态配置和动态配置。所谓静态配置其实就是在软件启动时的一些配置,运行时基本不会进行修改,也可以理解为是环境或软件初始化时需要用到的配置。
例如,操作系统的网络配置,软件运行时 Docker 进程的配置,这些配置在软件环境初始化时就确定了,未来基本不会修改了。而所谓动态配置其实就是软件运行时的一些配置,在运行时会被修改。比如,日志级别、降级开关、活动开关。
当然,我们这里的内容主要针对动态配置的管理。
对于动态配置的管理,我们还要做好区分。一般来说,会有三个区分的维度。
按运行环境分。一般来说,会有开发环境、测试环境、预发环境、生产环境。这些环境上的运行配置都不完全一样,但是理论来说,应该是大同小异的。
按依赖区分。一种是依赖配置,一种是不依赖的内部配置。比如,外部依赖的 MySQL 或 Redis 的连接配置。还有一种完全是自己内部的配置。
按层次分。就像云计算一样,配置也可以分成 IaaS、PaaS、SaaS 三层。基础层的配置是操作系统的配置,中间平台层的配置是中间件的配置,如 Tomcat 的配置,上层软件层的配置是应用自己的配置。
这些分类方式其实是为了更好地管理我们的配置项。小公司无所谓,而当一个公司变大了以后,如果这些东西没有被很好地管理起来,那么会增加太多系统维护的复杂度。

配置中心的模型

有了上面为配置项的分类,我们就可以设计软件配置模型了。
首先,软件配置基本上来说,每个配置项就是 key/value 的模型。
然后,我们把软件的配置分成三层。操作系统层和平台层的配置项得由专门的运维人员或架构师来配置。其中的 value 应该是选项,而不是让用户可以自由输入的,最好是有相关的模板来初始化全套的配置参数。而应用层的配置项,需要有相应的命名规范,最好有像 C++ 那样的名字空间的管理,确保不同应用的配置项不会冲突。
另外,我们的配置参数中,如果有外部服务依赖的配置,强烈建议不要放在配置中心里,而要放在服务发现系统中。因为一方面这在语义上更清楚一些,另外,这样会减少因为运行不同环境而导致配置不同的差异性(如测试环境和生产环境的不同)。
对于不同运行环境中配置的差异来说,比如在开发环境和测试环境下,日志级别是 Debug 级,对于生产环境则是 Warning 或 Error 级,因为环境的不一样,会导致我们需要不同的配置项的值。这点需要考虑到。
还有,我们的配置需要有一个整体的版本管理,每次变动都能将版本差异记录下来。当然,如果可能,最好能和软件的版本号做关联。
我们可以看到,其中有些配置是通过模板来选择的,有的配置需要在不同环境下配置不同值。所以,还需要一个配置管理的工具,可能是命令行的,也可以是 Web 的。这个工具的界面在文本中(下面这个 UI 的 mockup 只是想表明一个模型)。
用户可以根据不同的机器型号还有不同的环境直接调出后台配置好的相关标准配置的模板。对于一些用户需要自己调整的参数也可以在这个模板上进行调整和配置(当然,为了方便运维和管理最好不要进行调整)。然后,用户可以在下面的那个表格中填写好自己的应用要用的参数和各个环境中的值。
这样一来,这个工具就可以非常方便地让开发人员来配置他们自己的软件配置。而我们的配置中心还需要提 API 来让应用获取配置。这个 API 上至少需要有如下参数:服务名,配置的版本号,配置的环境。

配置中心的架构

接下来,要来解决配置落地的问题。我们可以看到,和一个软件运行有关系的各种配置隶属于不同的地方,所以,要让它们落地还需要些不一样的细节要处理。文本中,我们给了一个大概的架构图。
在这个图中可以看到,我们把配置录入后,配置中心发出变更通知,配置变更控制器会来读取最新的配置,然后应用配置。这看上去很简单,但是有很多细节问题,下面我来一一说明。
为什么需要一个变更通知的组件,而不是让配置中心直接推送? 原因是,分布式环境下,服务器太多,推送不太现实,而采用一个 Pub/Sub 的通知服务可以让数据交换经济一些。
为什么不直接 Pub 数据过去,还要订阅方反向拉数据? 直接推数据当然可以,但让程序反过来用 API 读配置的好处是,一方面,API 可以校验请求者的权限,另一方面,有时候还是需要调用配置中心的基本 API,比如下载最新的证书之类的。还有就是,服务启动时需要从服务中心拉一份配置下来。
配置变更控制器部署在哪里?是在每个服务器上呢,还是在一个中心的地方? 我觉得因为这个事是要变更配置,变更配置又是有很多步骤的,所以这些步骤算是一个事务。为了执行效率更好,事务成功率更大,建议把这个配置变更的控制放在每一台主机上。
平台层的配置变更,有的参数是在服务启动的命令行上,这个怎么变更呢? 一般来说,命令行上的参数需要通过 Shell 环境变量做成配置项,然后通过更改系统环境变量,并重启服务达到配置变更。
操作系统的配置变更和平台层的配置变更最好模块化掉,就像云服务中的不同尺寸的主机型号一样。 这样有利于维护和减少配置的复杂性。
应用服务配置更新的标准化。 因为一个公司的应用由不同的团队完成,所以,可能其配置会因为应用的属性不同而不一样。为了便于管理,最好有统一的配置更新。一般来说,有的应用服务的配置是在配置文件中,有的应用服务的配置是通过调用 Admin API 的方式变更,不同的应用系统完全不一样,你似乎完全没有方法做成统一的。这里给几个方案。
可以通过一个开发框架或 SDK 的方式来解决,也就是应用代码找你这个 SDK 来要配置,并通过 observer 模式订阅配置修改的事件,或是直接提供配置变更的 Admin 的 API。这种方式的好处在于在开发期标准化,并可以规范开发;不好的是,耦合语言。
通过一个标准应用运维脚本,让应用方自己来提供应用变更时的脚本动作。这种方式虽然通过运维的方式标准化掉配置变更的接口,就可以通过一个配置控制器来统一操作各个应用变更,但是在这个脚本中各个应用方依然使用着各种不同的方式来变更配置。这种方式的好处是不耦合语言,灵活,但对于标准化的建设可能不利,而且使用或者调用脚本是 Bug 很多的东西,容易出问题。
或是结合上述两种方案,不使用开发阶段的 SDK 方式嵌入到应用服务中,而是为每个应用服务单独做一个 Agent。这个 Agent 对外以 Admin API 的方式服务,后面则适配应用的配置变更手段,如更新配置文件,或者调用应用的 API 等。这种方式在落地方面是很不错的(这其中是另一种设计模式,后面会讲到)。

配置中心的设计重点

配置中心主要的用处是统一和规范化管理所有的服务配置,也算是一种配置上的治理活动。所以,配置中心的设计重点应该放在如何统一和标准化软件的配置项,其还会涉及到软件版本、运行环境、平台、中间件等一系列的配置参数。如果你觉得软件配置非常复杂,那么,你应该静下心来仔细梳理或治理一下现有的配置参数,并简化相应的配置,使用模块会是一种比较好的简化手段。
根据我们前面《编程范式游记》中所说的,编程的本质是对 logic 和 control 的分离,所以,对于配置也一样,其也有控制面上的配置和业务逻辑面上的配置,控制面上的配置最好能标准统一。
配置更新的时候是一个事务处理,需要考虑事务的问题,如果变更不能继续,需要回滚到上个版本的配置。配置版本最好和软件版本对应上。
配置更新控制器,需要应用服务的配合,比如,配置的 reload,服务的优雅重启,服务的 Admin API,或是通过环境变量……这些最好是由一个统一的开发框架搞定。
配置更新控制器还担任服务启动的责任,由配置更新控制器来启动服务。这样,配置控制器会从配置中心拉取所有的配置,更新操作系统,设置好启动时用的环境变量,并更新好服务需要的配置文件 ,然后启动服务。(当然,你也可以在服务启动的脚本中真正启动服务前放上一段让配置更新控制器更新配置的脚本。无论怎么样,这些都可以在运维层面实现,不需要业务开发人员知道。)

小结

好了,我们来总结一下今天分享的主要内容。首先,传统单机软件的配置通常保存在文件中,但在分布式系统下,为了管理方便,必须有一个配置中心。然后我讲了配置的区分:按静态和动态、运行环境、依赖和层次来区分。进一步,从区分出的情况出发,层次方面,平台、中间件和应用三个层次由不同职责的运维人员来配置。
外部依赖的配置并不适合放在配置中心里,而最好是由服务发现系统来提供。开发环境和生产环境的日志级别配置也会不同。出于这些特点,可以用一个配置管理工具来管理这些配置。接着,我介绍了配置管理架构中几个关键问题的解决思路。最后,我介绍了配置中心的几个设计重点。下一讲中,我们讲述边车模式。希望对你有帮助。
也欢迎你分享一下你的分布式系统用到了配置中心吗?它是怎样实现的呢?配置的动态更新是怎么处理的?有没有版本管理,和服务的版本又是怎样关联的呢?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
分享给需要的人,Ta购买本课程,你将得29
生成海报并分享

赞 12

提建议

上一篇
52 | 管理设计篇之“分布式锁”
下一篇
54 | 管理设计篇之“边车模式”
unpreview
 写留言

精选留言(26)

  • 曾经的十字镐
    2018-04-25
    耗子哥这篇文章讲的非常好也非常全,但是我还想发表一下我的看法,我觉得配置中心应该根据实际情况来选择,我见过好多团队,其项目非常简单,就是一个分布式项目,4-5个模块,还搞了一个配置中心,实在有些重,我们项目也不大我使用mysql加定时拉取就搞定了,要搞清楚使用配置中心的目的,不要盲目的实用配置中心,这样你的系统就变得复杂了。
    共 1 条评论
    41
  • 繁泽
    2018-04-24
    耗子叔您好,请问 GitHub 上有没有一些不错的契合您写的设计思路的项目代码能推荐参考呢?
    共 1 条评论
    19
  • 约书亚
    2018-04-24
    我团队架构之前考虑过上配置中心,主要是应用配置方向,调研过携程Apollo,后反复论证暂时搁置,在期间我的思考如下: 1. 动静态的分类比较相对,实际开发常修改的配置大多是性能微调的参数和日志级别等,而前者应减少在生产环境的尝试。其他变更,因微服务的自动化技术,修改后重发布显得问题不大。通用/独有的中间件地址发生变更(比如failover)时,看起来很需要配置中心,但现在有各种流量调度技术 2. 很多配置变更需重建上下文,此类功能难写,框架少,而且担心在重新构建应用程序上下文期间带来服务性能下降。我们Java用SpringBoot,无以上问题...但如没有框架保障,还不如重发布,利用滚动更新+流量调度保证服务可用性 3. 这些配置是否还要出现在源代码配置文件中?如果是,没想好线上修改的配置项怎么保证同步到源代码。如果否,那上新服务和配置变更操作总有先后,要么配置细分小版本,每次服务发布都不同,要么有个灰度环境 以上为我们的情况,其实答案在皓哥文章中都有,但落地还需细节,望各位给出建议
    展开
    14
  • Field Li
    2018-05-27
    配置应该还是放在文件里,然后把文件推到agent上,每个机器本地存盘,这样即使配置中心挂了 服务依然可以从本地获取配置
    6
  • 迷宫中的将军
    2021-01-29
    不太推荐使用配置中心的方式,最好的就是和代码仓库一起,静态管理。 这种开发以及维护的成本比较高。同时生产环境上配置的变化开发人员不可见,不太符合IaC的原则。
    共 1 条评论
    4
  • 忙里偷闲
    2018-06-14
    这篇文章对于边车方式实现服务网格的思路相当清晰,如果要找对于这种思路的实现方案的话,kubernetes的istio应该是最贴近的实现。

    作者回复: 是的

    4
  • 121373628
    2018-05-19
    我们公司配置中心的使用分为业务相关配置和运维相关配置(Mq,zk,db相关的配置)。运维相关配置放在配置中心,开发人员无权操作。业务相关配置放在应用包里。然后发布通过统一发布系统发布。整个应用发布过程无需运维参与。比全部配置放配置中心,然后线上配置都需要运维修改的模式。效率提高很多。因为运维并不了解具体业务以及业务配置。运维操作更容易出错。在应用包里,可以开发环境就验证线上环境配置。不会出现配置多一个空格这种细小错误。导致上线才暴露问题。个人体会。
    展开
    共 1 条评论
    4
  • 2020-04-26
    配置中心可以使用zookeeper的状态来通知各个业务进程某个配置文件有变化,通知时只告知文件内容有变化,具体数据让业务进程重新去配置中心拉取。我们项目目前 是这样实现的。
    2
  • 知行合一
    2020-01-10
    配置中心设计的好的话可以做到灵活配置和版本控制等,而且会有本地缓存或者写文件,即使配置中心挂了,服务也能读取本地的配置保证可用性
    2
  • jerry
    2018-05-31
    没弄动态配置,把java的配置文件抽成配置模板,具体的配置值放到数据库的了,通过web进行增删改查,各个环境通过一个python脚本生成对应环境的配置文件 并发布到对应环境的机器上,脚本里实现了一个配置依赖,在一些环境里共享一些基础配置
    2
  • 龚极客
    2018-04-26
    请问下耗子哥,如果用docker镜像来管理,需要把配置文件打到包里吗?这样部署容易了,但是这样我需要开发,测试,线上三个包,感觉跟docker一套环境的初衷相违背
    共 1 条评论
    2
  • bing
    2018-04-25
    我们也有类似的配置中心服务,但是有一个担心,几乎所有有效配置按照设计都放在了配置中心系统上,如果配置系统挂掉,或者发布时有数据请求,怎么处理,这个是我们的担心点
    共 2 条评论
    2
  • 文刂 氵共 超
    2019-12-27
    坚持学习,学习笔记 https://mubu.com/colla/7I6BTsJBXoM
    1
  • walle斌
    2022-11-07 来自北京
    配置中间的加密问题。。
  • LiuHu
    2022-03-24
    “如果有外部服务依赖的配置,强烈建议不要放在配置中心里,而要放在服务发现系统中”。 耗子哥,能详细解释一下 “服务发现系统” 是什么吗?这和我们常用的 Eureka 、Nacos 服务发现不是一个概念吧。
  • 方勇(gopher)
    2021-11-09
    依赖,用的服务发现,注册中心eureka.业务配置用的Apollo
  • 亢(知行合一的路上)
    2020-03-16
    分布式系统中的配置中心解决了对大量服务的配置及其变更的问题。这个概念很重要,至于如何实现,是放在数据库,各服务定期拉取,还是变更通知,属于术的层面,可以根据项目情况来定。总之要对配置进行统一管理。
  • null
    2020-02-09
    day24 管理设计篇之配置中心 在分布式项目中,对于静态的配置我比较偏向于写在系统某个文件夹下的application.yml中让docker容器在启动的时候将配置文件映射到容器中,因为spring boot支持读取外置的yml配置文件且静态配置是几乎不变的.如果需要改变的话(小型项目服务器数量不多)只要登录服务器改文件,再重启容器即可 对于动态配置,老师给出了三种区分维度,运行环境,依赖区分,层次区分 运行环境的话我一般都会使用spring的profile机制在配置中心中创建多个applicationname-envname,yml来存储不同的代码运行环境 依赖区分,我觉得依赖外部的配置最好不要,因为像配置中心本身就是一个配置依赖外置,在配置中心的基础上将其他的一些配置放在mysql跟redis中会增加复杂度.引入依赖配置的情况一般是多个不同模块的实例都需要某些一样的配置,如果需要的模块都配置一份会造成冗余改起来也麻烦.我一般会使用类似运行环境的做法来避免,即在配置中心中创建一个application-xxxEnvName.yml,需要这些配置的模块的peifile会多一个xxxEnvNane来让它在启动的时候读取到这些配置,需要动态更新配置加上@RefreshScope注解就好了 层次区分,我觉得像开发的配置只能给开发者配置,运维的配置只能给运维配置,但我现在使用的nacos来作为配置中心并没有对于配置文件修改做账号权限控制,虽然我现在对于这个功能不需要(小公司运维的工作开发干),但如果项目发展大了就需要一个运维,但运维对于我们使用的spring boot yml不熟悉,更改的时候难免需要问我们
    展开
  • 番茄炒西红柿
    2020-01-12
    外部配置通过服务发现是怎么操作的
  • 蜗牛
    2019-07-21
    边车这个模式是跟着Application 走的,如果一台机器有多个应用就会有多个。既然是和业务无关的,是不是跟着机器走比较好呢。当然如果配合docker的话那就无所谓了。