Skip to content

16配置方案:如何设计分布式环境下的配置中心解决方案?

从今天开始,我们进入到配置中心的讨论。在微服务架构中,面对分散在各个服务、各个环境中的各种配置信息,配置中心是必备组件之一。Spring Cloud 中也专门提供了一个 Spring Cloud Config 框架来实现分布式配置中心。在引入这个框架之前,今天我们将先给出分布式配置的基本模型,并基于 Spring Boot 分析配置体系。

配置中心基本模型

在《追本溯源:究竟什么样的架构才是微服务架构?》中我们已经提到,微服务架构对于配置中心的需求一方面来自服务的数量。在传统的单块系统中,对于配置信息的管理,往往倾向于把所有配置项跟源代码一起放在代码仓库中,这样比较简单,在确保配置信息安全性的同时,往往也可以运行得很好。但在微服务架构中,如果这样做,势必会导致很多配置信息重复出现在不同的服务中,造成浪费且增加维护成本。

另一方面,配置中心的需求来自配置信息的分散性。可以想象,在一个微服务系统中,势必存在多个服务,而这些服务一般都会存在开发环境、测试环境、预发布环境、生产环境等多套环境。针对不同的环境,我们都会采用一套不同的配置体系。那么如何保证多个环境中这些配置信息都能在各个服务实例中进行实时的同步更新呢?这就需要引入集中式配置管理的设计思想,如下图所示:

集中式配置中心示例图

在上图中,可以看到开发、测试和生产等不同环境的配置信息统一保存在一个配置中心中。而每个环境都构成了一个分布式集群,我们也需要保证每个集群中所有服务内部保存的同一份配置信息能够得到同步的更新。

考虑到服务的数量和配置信息的分散性,一般都需要引入配置中心的设计思想和相关工具。每一个微服务系统都应该有一个配置中心,而所有微服务中所使用到的配置信息都应该维护在配置中心中。对于配置中心的组成结构,我们可以做一层抽象,如下图所示:

配置中心组成结构

可以看到,对于一个典型的配置中心而言,存在两个组成部分,即配置服务器配置仓库

配置服务器的核心作用就是对接来自各个微服务的配置信息请求,这些微服务会通过配置服务器提供的统一接口获取存储在配置中心中的所需配置信息。因此,配置服务器也是作为独立的微服务而存在。对于配置服务器而言,一方面需要确保对配置中心中所存储的各种配置信息进行统一维护;另一方面,也需要提供一种通知机制 ,确保配置信息变化之后能够告知各个微服务,以便各个微服务及时更新本地服务中的配置数据。后者实际上跟《服务治理:如何正确理解服务治理解决方案?》中介绍的服务治理中基于轮询或监听机制的实现方式类似,只不过注册中心处理的是服务实例信息,而配置中心处理的是各种配置信息。

配置服务器可以独立完成配置信息的存储和维护 工作,但也可以把这部分工作剥离出来放到单独的一个媒介中,这个媒介就是配置仓库。请注意,配置仓库并不是必备的,我们完全可以依托配置服务器自身的文件系统来实现配置信息的存储。但构建独立配置仓库的主要优势在于能够把配置存储过程进行抽象,从而支持 SVN、Gitlab 等具备版本控制功能的多种第三方工具,以及自建一个具有持久化或内存存储功能的存储媒介。

构建一个完整的配置中心并不简单,需要具备很多功能特性。首当其冲的就是隔离性,这里的隔离性指的是不同环境下配置信息之间应该是不会相互混淆的,例如测试环境的配置显然不应该作用于生产环境。然后,我们也需要有一种高效的机制来确保配置中心中的配置信息一旦有任何变化,能够及时通知到各个微服务,这也是配置一致性的一种表现。

配置中心实现工具

基于配置中心的实现需求,业界存在一批典型的分布式配置中心实现工具。这里列举部分知名的开源工具并给出了相应的特性描述。

首先是两款比较典型的配置中心实现工具 ,一款是Etcd ,它是一个轻量级分布式 Key-Value 对数据库,实现了数据更新、目录监听、分布式锁等功能特性。而Consul是一款由 Google 提供的开源框架,内置了服务注册与发现框架以及分布一致性协议实现机制,既可以用作配置中心,也可以用于构建注册中心。

在国产软件中,Disconf是由百度开源的一个分布式配置管理工具,与 Spring 框架有很好的集成,在使用上也提供了友好的 Web 管理界面。从实现机制上讲,Disconf 把配置信息都存储在MySQL中,并基于 Zookeeper 提供的监听机制实现数据的实时推送。而 Diamond 则由阿里巴巴提供,同样把配置数据存储在 MySQL 上,但在获取配置数据时不是使用的推送模式,而是每隔一段时间进行全量数据的拉取,实现过程比较简单。相较 Disconf 同时提供的基于配置文件和 Key-Value 对的数据管理模式,Diamond 只支持 Key-Value 对结构的数据。

讲到配置中心,就不得不提到Zookeeper。Zookeeper 是一种分布式协调工具,它对每个节点都可以设置监听器 Watcher。节点监听机制可以用来实现实时感知,即当某一个节点的信息有任何变动时,所有监听该节点的其他节点都可以实时获取通知,从而做出响应。对于配置中心而言,所有服务就是 Zookeeper 的客户端,这些服务通过对包含配置信息的 Zookeeper 节点进行监听就能获取配置信息更新内容。基于 Zookeeper 实现配置中心的示意图如下所示,可以看到这个过程本质上就是对 Zookeeper 节点和监听器的合理利用。

Zookeeper 实现配置中心示例图

最后要介绍的配置中心实现工具是 Spring 家族中的Spring Cloud Config。从设计思路上讲,Spring Cloud Config 与 Zookeeper 完全不同。Zookeeper 是把配置信息保存在其内部的节点上,这些节点本质上就是操作系统的文件系统。而 Spring Cloud Config 同样可以将配置信息保存在文件系统中,但更多的场景是推荐使用 Git 等配置仓库来存储配置信息。在关键的配置变化通知机制上,Zookeeper 依赖其变更事件的发送和 Watcher 机制来通知客户端;而 Spring Cloud Config 则会发送事件到 Spring Cloud Bus 消息总线,然后由消息总线通知相关的服务。

显然,不同的工具具有不同的设计原理和实现方式,在本课程的后续内容中,我们会基于 Spring Cloud Config 讨论配置中心应用方式和实现原理。而在此之前,我们有必要对 Spring Boot 中的配置体系做一些介绍。

Spring Boot 中的配置体系

我们知道 Spring Boot 是 Spring Cloud 构建单个微服务的基础,所以但凡涉及服务配置,最终也是通过 Spring Boot 中的配置体系进行承载。而 Spring Boot 的配置体系有其自身的特点,我们来看一下。

在 Spring Boot 中,对配置文件的命名是有一定规范的,引入了 Label 和 Profile 概念指定配置信息的版本以及运行环境。其中 Label 表示配置版本控制信息,而 Profile 则用来指定该配置文件所对应的环境,例如分别使用 dev、prod、test 来对应开发、生产和测试环境。当然,我们也可以根据需要自定义各种 Profile。

Spring Boot 中配置文件的格式有两种,分别是 .properties 格式和 .yml 格式。结合 Label 和 Profile 概念和不同的文件格式,如下所示的配置文件在命名上都是合理的:

xml
/{application}.yml
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

.yml 文件中所使用的语法和其他高级语言类似,特别适合用来表达或编辑数据结构和各种配置文件。以常见的数据源配置为例,如果采用 .yml 文件则可能是如下所示的配置效果。

xml
datasource:
	driverClassName: com.mysql.jdbc.Driver
	url: jdbc:mysql://localhost/springhealth
    username: root
    password: root

而如果采用 .propertie 配置文件,那么上述配置信息将表示为如下的形式:

xml
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/springhealth
spring.datasource.username=root
spring.datasource.password=root

显然,类似这样的数据源通常会根据环境的不同而有很多套配置。假设我们存在如下所示的配置文件集合,注意这里有一个全局的 application.properties 配置文件以及多个局部的 Profile 配置文件。

xml
application.properties
application-uat.properties
application-test.properties
application-prod.properties

那么,如何指定当前使用具体哪一套配置信息呢?在 Spring Boot 中,我们可以在主 application.properties 中使用如下的配置方式来激活当前所使用的 Profile:

xml
spring.profiles.active = test

上述配置项意味着系统当前会读取 application-test.properties 文件中的配置信息。同样,如果使用 .yml 文件,则可以使用如下所示的配置方法。

xml
spring:
  profiles:
    active: test

事实上,我们也可以同时激活几个 Profile,这完全取决于你对系统配置的需求和维度。

xml
spring.profiles.active: test, myprofile1, myprofile2

当然,如果你想把所有的 Profile 配置信息只保存在一个文件中而不是分散在多个配置文件中, Spring Boot 也是支持的,需要做的事情只是对这些信息按 Profile 进行分段组织,如下所示:

xml
spring: 
    profiles: test
    #test环境相关配置信息
 
spring: 
    profiles: prod
    #prod环境相关配置信息

尽管上述方法是有效的,但在本课程中,还是推荐你按多个配置文件的组织方法管理各个 Profile 配置信息,这样不容易混淆和出错。

最后,如果我们不希望在全局配置文件中静态指定所需要激活的 Profile,而是想把这个过程延迟到运行这个服务时,那么可以直接在 java --jar 命令中添加"--spring.profiles.active"参数,如下所示

xml
java -jar user-service.jar --spring.profiles.active=prod

这种实现方案在通过脚本进行自动化打包和部署的场景下非常有用。

小结与预告

配置中心是微服务架构中的一个基础组件,而业界关于如何实现配置中心也有一些基本的模型和工具。本课时针对配置中心实现需求梳理了涉及一款配置中心所必须要考虑的数据存储、变更通知等核心问题,并结合业界主流的开源框架做了对比和分析,并最终选择 Spring Cloud 家族中的 Spring Cloud Config 来作为本课程的配置中心实现方案。同时,因为 Spring Cloud Config 依赖于 Spring Boot 中的配置体系,我们也对这个换题做了一些展开。

这里给你留一道思考题:在配置中心中,如果想要实现配置信息的变更通知,一般有哪些设计和实现的思路?

接下来,我们就将全面讨论 Spring Cloud Config 框架。我们会分两个课时分别从服务器端和客户端出发对该框架的应用方式和实现原理进行解析。下一课时,我们先来讨论服务器端组件。