重学SpringCloud系列四之分布式配置中心---上
服务配置中心概念及使用场景
一、为什么要进行统一配置管理
为了避免参数变化引起的频繁的程序改动,通常我们在应用程序中将常用的一些系统参数、启动参数、数据库参数等等写到配置文件或其他的存储介质里面。
- 配置常见的存储方式:配置文件、数据库等
- 配置对于应用程序是只读的,程序通过读取配置来影响程序的运行行为
- 配置是区分环境的同一份程序部署到生产、测试、开发、演示环境下,需要做不同的配置
传统应用程序的配置分散,导致了在进行部署、运维方面,需要极大的成本。那么传统的应用程序配置面临哪些问题?
问题一:应用程序多实例集群部署,每个微小的配置的修改将导致每个实例都需要重新打包部署

问题二:每一套环境的配置不同,难于维护,增加了人工犯错的几率

问题三:没有严格的配置管理权限控制,导致公司的核心数据泄露
不知道大家有没有看过一条报道,国外某著名的公司,在开源代码的数据库连接配置中,携带了其"生产环境"的数据库配置信息,导致其核心的用户数据泄露。
除了上面的三点,还有很多传统的配置管理方式面临的问题,所以我们要进行集中的统一的配置管理。这点在微服务应用中体现的更为明显。
二、分布式配置管理中心
比起“分布式配置中心”这个词,我更喜欢集中的配置管理中心叫做“统一配置管理中心”。“分布式”修饰的是应用程序,而“统一”才是修饰配置管理中心的关键词。但是大家都叫分布式配置管理中心,我也就从了大家。但是在理解上要区分过来。
理想的配置管理中心应该是:
- 支持多应用配置管理
- 支持多环境(生产、测试等)配置管理
- 支持配置权限管理
- 支持配置版本化管理、配置回滚
- 支持配置的动态发布、灰度发布(后文会给大家介绍)

配置中心将配置从应用中剥离出来,统一管理,优雅的解决了配置的动态变更、持久化、运维成本等问题。应用自身既不需要去添加管理配置接口,也不需要自己去实现配置的持久化,更不需要引入“定时任务”以便降低运维成本。总得来说,配置中心就是一种统一管理各种应用配置的基础服务组件。
在系统架构中,配置中心是整个微服务基础架构体系中的一个组件,如下图,它的功能看上去并不起眼,无非就是配置的管理和存取,但它是整个微服务架构中不可或缺的一环。
三、主流配置中心
目前市面上用的比较多的配置中心有:
Spring Cloud Config
2014年9月开源,Spring Cloud 生态组件,可以和Spring Cloud体系无缝整合。
https://github.com/spring-cloud/spring-cloud-config
Apollo
2016年5月,携程开源的配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
https://github.com/ctripcorp/apollo
Nacos
2018年6月,阿里开源的配置中心,也可以做DNS和RPC的服务发现。
https://github.com/alibaba/nacos
如何选择
- 如果你希望完成单纯的分布式配置集中管理,其实三者都能满足你的需求。
- 如果你考虑到已经用Nacos实现了服务注册中心,不想单独搞出来一个配置管理中心,合二为一的话,nacoos可能是你的最佳选择
- 携程的Apollo与nacos很多相似之处,有颇多的亮点。从笔者的使用感受而言,目前apollo从文档细节到方便度要好于nacos(截止2020年4月)。但是nacos毕竟开源时间较短,依托alibaba的支持,有很大的潜力和发展空间。
- spring cloud config对比其他两者,在功能以及友好度方面都逊色。唯一的优点可能是它比较轻量级。
| 对比项目/配置中心 | spring cloud config | apollo | nacos(重点) |
|---|---|---|---|
| 开源时间 | 2014.9 | 2016.5 | 2018.6 |
| 配置实时推送 | 弱支持(Spring Cloud Bus) | 支持(HTTP长轮询1s内) | 支持(HTTP长轮询1s内) |
| 版本管理 | 支持(Git) | 自动管理 | 自动管理 |
| 配置回滚 | 弱支持(Git+Bus) | 支持 | 支持 |
| 配置的灰度发布 | 理念上支持,可操作性不强 | 支持 | 1.1.0开始支持 |
| 权限管理 | 不支持(没有区分用户、角色、权限的概念) | 支持 | 1.2.0开始支持 |
| 多集群多环境 | 对集群概念支持较弱 | 支持 | 支持 |
| 多语言 | 只支持Java | Go,C++,Python,Java,.net,OpenAPI | Python,Java,Nodejs,OpenAPI |
| 分布式高可用最小集群数量 | Config-Server2+Git+MQ | Config2+Admin3+Portal*2+Mysql=8 | Nacos*3+MySql=4 |
| 配置格式校验 | 不支持 | 支持 | 支持 |
| 通信协议 | HTTP和AMQP | HTTP | HTTP |
| 数据一致性 | Git保证数据一致性,Config-Server从Git读取数据 | 数据库模拟消息队列,Apollo定时读消息 | HTTP异步通知 |
SpringCloudConfig配置中心
Spring Cloud Config简介
Spring Cloud Config Server提供了可水平扩展的集中式配置服务。它使用可插拔的存储库层作为数据存储,该存储层目前支持本地存储,Git和Subversion。其核心功能:
- 通过将版本控制系统用作配置存储,开发人员可以轻松地对配置更改进行版本控制和审核。
- 实现集中的配置管理,不同的环境、不同应用的配置通过文件名称进行区分。
- 支持运行时动态配置更新,即:配置的热更新
- 提供配置访问的REST接口

- 首先我们需要一个远程的
Git Repository仓库(在实际生产环境中,一般需要自己搭建一个Git服务器。方便起见,建议使用了开源中国的gitee仓库或微软的github) - 其次我们需要搭建
ConfigSever,Spring Cloud微服务(如上图A、B、C)应用在启动的时候会从Config Server中来加载相应的配置信息 。前提是Spring Cloud微服务集成了Spring Cloud Config的客户端程序。 - 当
Spring Cloud微服务尝试去从Config Server中加载配置信息的时候,Config Server会先通过git clone命令从远程Git Repository仓库克隆一份配置文件保存到本地 。这样当Git Repository远程仓库无法连接时,就直接使用Config Server本地存储的配置信息 - 由于配置文件是存储在
Git仓库中,所以配置文件天然的具备版本管理功能,Git中的Hook功能可以实时监控配置文件的修改
构建git配置文件仓库
虽然Spring Cloud config目前支持本地存储,Git和Subversion,但是基于配置版本审核、管理,以及可用性的考量基础,几乎最终都是选择git作为Spring Cloud config的配置仓库的管理工具。
考虑github连接太慢,下面使用gitee作为数据存储服务器
按照下面四个步骤我们来配置文件仓库:
git clone 远程库地址
git add 文件名 : 将工作区的文件添加到暂存区
git commit -m "日志信息" 文件名 : 将暂存区的文件提交到本地库
将本地的master分支推送到origin主机,同时指定origin为默认主机,后面就可以不加任何参数使用git push了。
git push -u origin master 使用master分支,将本地仓库里面的内容提交到远程仓库
- 用你自己的账号在gitee上新建一个作为配置中心数据存放的仓库

- 将仓库克隆到本地的一个文件夹中


3.在克隆下来的仓库中新建配置文件,将之前系列中的rabc项目配置文件和sms配置文件复制进去,并进行改名,改名规则如下:
其文件命名规则为:{application}-{profile}.yml
-
application表示项目的名称,即:spring.application.name的配置值 -
profile代表基础环境,通常是指:pro(生产)、dev(开发)、test(测试)等等。

4.添加文件到暂存区–>提交本地库


5.推送到远程仓库

将以上的本地配置文件及文件夹与远程仓库(gitee或github)同步,我们的git仓库构建工作就完成了。

config配置中心搭建与测试
构建Config Server
和Eureka Server一样,netflix出品的Config Server也是基于SpringBoot项目的。
所以我们在spring-cloud新建一个module:dhy-server-config。

如果没有搭建父工程的,可以选择参考之前系列进行搭建,或者只引入相关依赖,测试使用
通过maven坐标引入关键类库:spring-cloud-config-server
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>StudyCloudWithMe</artifactId>
<groupId>dhy.xpy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dhy-server-config</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
</project>
在项目启动类上面加上@EnableConfigServer注解

在application.yml中进行config server的基本配置。
server:
port: 8771 #端口自定义
spring:
application:
name: dhy-server-config #config server项目名称
cloud:
config:
server:
git:
uri: https://gitee.com/DaHuYuXiXi/config-configuration-warehouse.git
#searchPaths: zimug-server-config-repo
username: xxx
password: xxx
#读取分支
label: master
- spring.cloud.config.server.git.uri:配置git仓库位置的http访问地址
- spring.cloud.config.server.git.searchPaths:配置仓库路径下的相对搜索位置,可以配置多个。
- spring.cloud.config.server.git.username:git仓库的用户名
- spring.cloud.config.server.git.password:git仓库的用户密码
如果把配置文件放在目录中提交上去,那么searchPaths就是该目录的名字,指明我们需要的配置文件在哪个目录下面
config server访问测试
config server构建完成之后,我们可以通过浏览器URL访问测试,读取配置文件。
其配置文件的读取与URL之间的映射关系如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
-
{application}就是应用名称,对应到配置文件上来,就是配置文件的名称部分,例如我上面创建的配置文件。 -
{profile}就是配置文件的版本,我们的项目有开发版本、测试环境版本、生产环境版本,对应到配置文件上来就是以
application-{profile}.yml加以区分,例如application-dev.yml、application-sit.yml、application-prod.yml。 -
{label}表示git分支,默认是master分支,如果项目是以分支做区分也是可以的,那就可以通过不同的label
来控制访问不同的配置文件了。
所以我们的这两个配置文件,可以通过如下的URL查看配置信息(以dhy-service-sms-dev.yml为例):

- http://localhost:8771/dhy-service-sms-test.yml
- http://localhost:8771/master/dhy-service-sms-test.yml
- http://localhost:8771/dhy-service-sms/test/master
- http://localhost:8771/dhy-service-sms/test

通过访问以上地址,如果可以正常返回数据,则说明配置中心Config Server服务端一切正常。至此,说明config server和git远程仓库之间的配置同步已经通了(红色边框部分)。

但是,大家可以明显的感觉到这里遗留了一个问题:
那就是任何一个人都可以通过浏览器URL去访问任何一个项目的配置文件。关于Spring Cloud config配置的安全与权限管理做的肯定是没有apollo那么好,但是也是有一些可以自己实现的安全认证方式,否则就太不安全了。我们后面的章节会讲到。
config客户端基础

从上图中,通过前面章节的讲解,我们已经实现了如下内容
- 建立
Git Repository仓库,我们已经可以向仓库提交配置文件了 - 搭建了
Config Server做统一的配置管理,可以从配置仓库拉取配置文件
本节就为大家讲解第三步:微服务(config 客户端)从config server获取配置的方法。
配置工作
在dhy-service-sms和dhy-service-rbac服务的pom.xml中引入spring-cloud-starter-config依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
src/main/resources/bootstrap.yml配置(以dhy-service-sms为例),来指定config server,例如:
spring:
application:
name: dhy-service-sms
cloud:
config:
uri: http://localhost:8771
label: master
profile: dev
- spring.cloud.config.profile:对应前配置文件中的{profile}部分
- spring.cloud.config.label:对应前配置文件的git分支
- spring.cloud.config.uri:config server配置中心的地址
SpringCloud-Config-Client配置文件为什么一定要是bootstrap.yml或者bootstrap.properties
当使用 Spring Cloud Config Server 的时候,你应该在 bootstrap.yml 里面指定 spring.application.name 和spring.cloud.config.server.git.uri和一些加密/解密的信息
加载过程:在Spring Cloud Config 项目 中configclient 服务启动后,默认会先访问bootstrap.yml,然后绑定configserver,然后获取application.yml 配置。如果仅仅在application.yml 配置了url:http://127.0.0.1:8080 这样默认会使用8888端口(配置无效)。 所以, 我们将绑定configserver的配置属性应该放在bootstrap.yml文件里。
以application.yml 为配置文件启动日志会有如下信息:
[z_cloud2_config_server_cluster_client] INFO org.springframework.cloud.config.client.ConfigServicePropertySourceLocator - Fetching config from server at: http://localhost:8888
[z_cloud2_config_server_cluster_client] WARN org.springframework.cloud.config.client.ConfigServicePropertySourceLocator - Could not locate PropertySource: I/O error on GET request for "http://localhost:8888/zCloud2ConfigServerClusterClient/default": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect
[z_cloud2_config_server_cluster_client] INFO
当使用 Spring Cloud 的时候,配置信息一般是从 config server 加载的,为了取得配置信息(比如密码等),你需要一些提早的或引导配置。
因此,把 config server 信息放在 bootstrap.yml,用来加载真正需要的配置信息。
这是由spring boot的加载属性文件的优先级决定的,你想要在加载属性之前去spring cloud config server上取配置文件,那spring cloud config相关配置就是需要最先加载的,而bootstrap.properties的加载是先于application.properties的,所以config client要配置config的相关配置就只能写到bootstrap.properties里了。
最后,我们把application.yml中的配置全部注释掉(如下图)。因为这部分配置,我们已经全都放到git 仓库中进行集中管理,我们的服务通过config server就可以获取到。

结果验证
- 首先启动日志,明确的输出:
dhy-service-sms项目在启动的时候去config server:http://localhost:8771加载配置。加载的是dhy-service-sms的master分支的dev环境的配置文件,也就是:https://gitee.com/DaHuYuXiXi/config-configuration-warehouse.git/dhy-service-sms-dev.yml,完全和我们的预期一致(和本文上面内容的配置一致)。

- 然后我们再看dhy-service-sms是否向eureka成功的进行了服务注册

eureka相关的配置不是注释掉了么?
是本地应用注释掉了,但是我们已经把它转移到远程配置管理仓库中了。
这也再次验证了,aservice-sms正确的应用了远程git仓库和config server进行集中的配置管理。
config配置安全认证

在前面章节我们已经为大家介绍了Spring Cloud config进行配置管理的基本流程。在使用Config Server的时候,我们可以通过一些固定模式的http-URL,没有任何限制的访问到项目的配置文件信息,这样很不安全。
为了解决这个问题,我们可以使用spring security进行简单的basic安全认证(也可自定义认证方式,这里不做扩展,需要深入去学习Spring Security)
Config Server服务端改造
在dhy-server-config的pom文件中增加依赖:
<dependency>
<!-- spring security 安全认证 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置文件application.yml增加security配置:
spring:
security:
basic:
enabled: true #启用基本认证(默认)
user:
name: xxx #自定义登录用户名
password: xxx #自定义登录密码
启动服务,测试一下。请求 http://localhost:8771/dhy-service-sms-dev.yml ,会发现弹出了security的basic登录验证:

输入我们自定义的用户名密码,返回请求配置信息。这种方式实际上也是一种简陋的安全认证方式,但总比没有强。
微服务客户端改造
当config server增加了登录认证之后,我们的微服务客户端想要正确的获取配置信息,在发送请求的时候也要携带用户名密码。
修改配置文件bootstrap.yml,增加username和password配置:

结果验证
首先启动config server服务端项目:dhy-server-config,然后启动dhy-service-sms和dhy-service-rbac服务。
- 一是日志中明确输出了获取到对应的配置文件信息。
- 二是微服务正确的向服务注册中心eureka’进行了注册。

至此,spring cloud config安全配置完成~
config客户端配置刷新
配置发生更改之后,将配置值的结果更新到客户端程序中。因此我们首先要明白两个问题:
- 哪些配置刷新之后可以更新,哪些配置刷新之后也无效?
- 第一类是影响应用运行状态的配置,这一类的配置通常会影响Spring Bean的自动装载。比如:数据库连接配置,在应用启动的时候会自动根据数据库配置初始化一个数据库连接池,连接池中保存着n个激活的数据库连接,以供业务持久化操作调用。这一类的配置是不能热更新的,或者准确的说即使配置数据本身更新了也没有用,数据库用户名密码配置更新了不等于数据库连接池里面的连接对象也更新了。配置背后的应用对象重构工作,config是无法帮你做到的(配置更新后只有应用重启才能生效)
- 第二类是业务运行所需的数据,比如:新建用户时的默认密码,重置用户时的默认密码。这一类的配置发生变更修改的就是配置数据本身,它不去影响程序的其他对象,不产生其他的连锁反应。
2.spring cloud config可以对哪些注解标注的配置进行刷新。下面两个例子都可以将"user.init.password"键对应的值热更新到password和defaultPwd对象上。
3.这两个注解需要结合@RefreshScope注解使用才能使配置热更新生效。
@RefreshScope //这里需要加上RefreshScope注解
@ConfigurationProperties(prefix = "user.init")
public class User{
private String password;
}
下文中会针对这种@Value注解的方法为例进行讲解。
使微服务客户端具备手动配置刷新能力
因为config底层是基于RefreshEndpoint实现的配置刷新,因此需要引入actuator相关依赖,开放相关的端点
该依赖是加在客户端,是客户端需要刷新配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
actuator为我们提供“/actuator/refresh”配置刷新接口
- management.endpoints.web.exposure.include=refresh,health,表示我们只开放配置刷新接口和健康检查接口,下面这段配置可以加载git仓库中对应的客户端yml文件中
例如: 我们这里需要动态刷新dhy-service-sms的配置信息,我们就将下面的配置加在gitee仓库中该微服务对应的yml文件里面
management:
endpoints:
web:
exposure:
include: refresh,health

在需要进行配置刷新的类上使用@RefreshScope,user.init.password对应的配置对象defaultPwd的值就初步具备刷新的能力。
@RestController
@RequestMapping("/sms")
@RefreshScope //不能忘记
public class SmsController {
@Value("${dhy.like}")
private Integer like;
@GetMapping(value = "/send")
public AjaxResponse send() {
System.out.println(like);
return AjaxResponse.success("短信发送成功,短信内容为: 大忽悠喜欢"+like);
}
}
测试:手动触发配置刷新
测试初始自定义配置是否生效
使用postman向“/sms/send”接口发送请求,测试配置是否能够正常拉取到

去gitee上面手动更改配置内容如下

当我们更改了gitee上面的配置文件后,配置中心config已经完成了最新版配置的拉取,还剩客户端需要从config配置中心手动拉取到最小的配置,下面验证config配置中心已经是最新的配置了

这里的乱码是因为没有提前告知浏览器返回的中文字符用什么字符集进行解析造成的,可能浏览器使用gbk解析,但是gitee上面是utf-8编码的
- 通过
POST请求发送到http://localhost:2333/actuator/refresh,触发dhy-service-sms的配置进行刷新,返回值是刷新了哪些配置项

客户端验证是否得到最新结果:

如何实现配置自动刷新
那么有没有一种方法,能够实现配置修改之后,自动去向http://localhost:2333/actuator/refresh发送请求,更新配置?
是有方法的,但是不好。实际生产中几乎没法用,不好也给大家说说,学习一下。
我们可以在Git仓库中配置一个webhook,所谓webhook的作用就是每当git仓库有接收到push代码请求时,都会去向自定义指定URL发送POST请求。我们完全可以利用webhook进行配置的自动刷新。

这是一种方法给大家放在这学习一下,但是笔者重来没这么做过,基于以下几点原因:
- 微服务客户端必须提供公网地址才能访问到,实际生产或开发环境谁会把自己的内部服务全部暴露到公网?(上图中的127.0.0.1要换成公网ip才可以,内网ip是无法访问到的)
- webhook发送请求是无法区分项目、无法区分环境的。该向哪一个项目的,哪一个环境,哪一个实例发送/actuator/refresh请求?不能随便配吧。
- 最重要的原因:程序员提交代码的行为不可控,不能因为配置代码变更了就认为这种变更是正确的,不代表可以自动的应用到环境中。
基于以上原因,都不如自己决定向哪里发送/actuator/refresh请求。甚至写一个简单的管理程序,都比使用webhook强。当然如果我们想通过spring cloud config实现微服务配置的全量刷新、批量刷新、局部刷新,还有终极解决方案,那就是结合Spring Cloud Bus使用,后面章节我们会讲到。
config配置中心高可用

Config Server实现高可用的原理比较简单:因为Config Server 是用Spring Boot构建的, 所以我们完全可以把它当做微服务注册到eureka,启动多个实例向eureka注册,并对外提供服务即可。
- 我们的业务服务(如dhy-service-sms)和Config Server都向Eureka注册,所以aservice-sms可以通过eureka获取ConfigServer服务列表
- dhy-service-sms通过负载均衡策略,在多个Config Server实例中选择一个作为获取配置请求的发送目标。
所以:
- 首先我们要有eureka集群,这个搭建我们前面已经讲过
- 其次我们要调整Config Server集成eureka客户端,从而使Config Server能够作为eureka客户端服务存在,实现服务注册与发现。
- 最后我们需要调整dhy-service-sms(既是Config Client、又是Eureka Client),以便适应从eureka获取Config Server服务列表。
Config Server
其实Config Server作为一个服务向eureka注册,和普通的微服务向eureka注册没有什么区别。
为了加深大家印象,我就再讲一遍:要想让Config Server实现服务注册功能,首先引入spring-cloud-starter-netflix-eureka-client。
<!-- spring cloud config 服务端包,这个包是一定存在的,否则就不是Config Server了-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- eureka client 端包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在application.yml加入eureka相关的配置
server:
port: 8771
spring:
application:
name: dhy-server-config
cloud:
config:
server:
git:
uri: xxxx
searchPaths: xxxx
username: xxxx
password: xxx
security:
basic:
enabled: true #启用基本认证(默认)
user:
name: xxxx
password: xxx
eureka:
client:
serviceUrl:
register-with-eureka: true
fetch-registry: true
defaultZone: http://dhy:centerpwd@peer1:8761/eureka/eureka/,http://dhy:centerpwd@peer2:8761/eureka/eureka/,http://dhy:centerpwd@peer3:8761/eureka/eureka/
instance:
preferIpAddress: true
在dhy-server-config的启动类上加上@EnableEurekaClient注解

此时启动config server,再去看eureka注册中心,验证是否注册成功即可
Config Client
我们的微服务客户端(如:dhy-service-sms和dhy-service-rbac)作为config client需要调整配置。

- 原来直接通过URI访问config server单节点,现在调整为通过service-id到eureka注册中心发现config server服务集群。(上图红色边框部分)
- 另外需要加上eureka 客户端配置(上图橘色边框部分),why?这个不是不是已经放到仓库集中统一管理了么?
- 在没有实现高可用的config server之前,我们是URI直接访问config server,获取项目配置。响应结果配置中包含eureka
server配置,从而微服务可以向eureka集群注册。所以eureka客户端配置可以放在远程git仓库。 - 现在我们为了实现高可用,把config server作为eureka client注册到eureka集群。所以我们的其他本地服务想找到config server获取配置,必须先找到eureka服务注册中心,才能找到Config Server。所以eureka客户端配置必须放在本地服务中才行。











