抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Spring Cloud Config 配置中心

Spring Cloud Config

​ Spring Cloud Config微服务端和客户端提供了分布式系统的外部化配置支持。配置服务器为各应用的所有环境提供了一个中心化的外部配置。它实现了对服务端和客户端对Spring Environment和PropertySource抽象的映射,所以它除了适用于Spring构建的应用程序,也可以在任何其他语言运行的应用程序中使用。作为一个应用可以通过部署管道来进行测试或者投入生产,我们可以分别为这些环境创建配置,并且在需要迁移环境的时候获取对应环境的配置来运行。

好处

​ 当我们系统业务扩展到一定程度的时候;免不了会增加很多的配置文件和信息,例如证书文件、接口对接的参数信息、数据库连接信息等;传统的单体式架构系统,SSH、SSM还是Struts等,只能是一个文件一个文件的增加堆积到项目系统中。每次更改配置信息的时候,都要重启服务器,影响线上业务浪费时间等。当配置文件数量达到一定程度的时候,整个项目就会看起来非常臃肿冗余,更甚者可能会拿错配置信息导致程序崩溃等。那么,这时候分布式系统采用的配置中心的优势就突出出来了。由业务拆分的多个模块系统的各配置文件,全部配置在配置中心统一管理;与程序分离,做到动态配置获取配置信息。无需重启服务器即可动态刷新加载配置信息。

组成部分

Spring Cloud Config 分为服务端和客户端两部分

服务端

​ 服务端也成为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器,并为客户端提供获取配置信息、加密解密信息灯访问接口

基础架构

根据上边的结构图,我们可以看到几个必要的元素:

  • 远程git仓库:用来存储配置文件的地方,多环境配置文件使用 hellxztest-{环境名}.yml
  • Config Server:分布式配置中心,指定了git仓库uri搜索路径访问账号密码
  • 微服务应用:配置客户端(Config Client),指定应用名配置中心url环境名分支名
启动流程
  1. 微服务应用启动,根据bootstrap.yml(properties)中配置的应用名(application)、环境名(profile)、分支名(label),向Config Server请求配置信息
  2. Config Server 根据自己bootstrap.yml(properties)中的Git(或SVN)仓库信息加上客户端传来的配置定位信息去查配置信息的路径
  3. Config Server 执行git clone命令,将配置信息下载到本地Git仓库中,将配置信息加载到Spring的ApplicationContext读取内容返回给客户端(微服务应用)
  4. 客户端将内容加载到ApplicationContext,配置内容的优先级大于客户端内部的配置内容,进行忽略

特殊情况: 当Config Server因为网络原因无法连接到Git或SVN时,客户端的请求过来后,会先连接Git或SVN,如果没连上,就使用本地仓库的配置文件内容进行返回给客户端

客户端

​ 客户端则是通过指定的配置中心来管理应用资源以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息,配置服务器默认使用 git 来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过 git 客户端工具来方便的管理和访问配置内容

能做什么

  • 集中管理配置文件
  • 不同环境不同配置,动态化的配置更新,分环境部署,比如dev/prod/test/beta/release
  • 运行期间动态调整配置,不再需要在每个服务上编写配置文件,服务会向配置中心统一拉取自己的配置
  • 当配置发生变动时,服务无需重启,可以动态的应用新配置
  • 将配置信息以 REST 接口的形式暴露给微服务

与GitHub整合配置

​ Spring Cloud Config 默认使用 Git 来存储配置文件(也有其他方式,比如SVN、本地文件,但最推荐的还是 Git),而且使用的是 http/https 访问的形式

bootstrap和application区别

用过 Spring Boot 的都知道在 Spring Boot 中有以下两种配置文件

  • bootstrap (.yml 或者 .properties) 是系统级的,优先级更高
  • application (.yml 或者 .properties) 是用户级的资源配置项

为什么会有这两种配置文件呢?大家都清楚它们的区别和具体使用场景吗?

​ Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap, 另外一种是 application, bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。

​ Spring Cloud 会创建一个 Bootstrap Context,作为 Spring 应用的 Application Context 的父上下文。初始化的时候,Bootstrap Context 负责从外部源加载配置属性,并解析配置。这两个上下文共享一个从外部获取的 Environment。

​ Bootstrap 属性有高优先级,默认情况下,它们不会被本地配置覆盖,Bootstrap Context 和 Application Context 有着不同的约定,所以新加一个 bootstrap文件,保证 Bootstrap Context 和 Application Context 配置的分离

因此,对比 application 配置文件,bootstrap 配置文件具有以下几个特性。

  • boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载
  • boostrap 里面的属性不能被覆盖

应用场景

application

​ application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。

bootstrap

bootstrap 配置文件有以下几个应用场景。

  • 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
  • 一些固定的不能被覆盖的属性
  • 一些加密/解密的场景;

基本使用

服务端准备

GitHub创建配置

使用 GitHub 或其它代码库创建一个仓库 springcloud-config,添加几个文件,创建一个 dev 分支

在config目录创建一些配置文件

编辑配置文件

1
2
3
4
5
6
7
8
9
eureka.client.serviceUrl.defaultZone=http://admin:admin@127.0.0.1:8888/eureka
# 续约更新时间间隔,一般设置比续约到期时间少,该配置表示,每隔30秒就向服务端发送心跳。
eureka.lease-renewal-interval-in-seconds=10
# 续约到期时间,可以单独给每个服务设置,如果在90秒(默认)内没有给服务发送心跳,则剔除该服务。
eureka.instance.lease-expiration-duration-in-seconds=90
# 每隔30秒就去注册中心拉取注册表信息。
eureka.client.registry-fetch-interval-seconds=30
#自定义测试版本号
order.version=1.0.0
创建服务端并添加依赖

新建一个项目当作配置中心,添加 maven 依赖

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
配置文件配置

application.properties添加如下配置,配置自己的远程仓库地址,如果 ssh 无法连接可以尝试使用 https

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server.port=8899
spring.application.name=config-server
eureka.client.serviceUrl.defaultZone=http://admin:admin@127.0.0.1:8888/eureka
# 续约更新时间间隔,一般设置比续约到期时间少,该配置表示,每隔30秒就向服务端发送心跳。
eureka.lease-renewal-interval-in-seconds=10
# 续约到期时间,可以单独给每个服务设置,如果在90秒(默认)内没有给服务发送心跳,则剔除该服务。
eureka.instance.lease-expiration-duration-in-seconds=90
# 每隔30秒就去注册中心拉取注册表信息。
eureka.client.registry-fetch-interval-seconds=30
# spring 配置中心
spring.cloud.config.server.git.uri=https://github.com/baiyunpeng/springcloud-config.git
#配置文件分支
spring.cloud.config.server.git.default-label=master
#配置文件所在根目录
spring.cloud.config.server.git.search-paths=config
创建启动类
1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
//启动ConfigServer
@EnableConfigServer
//注册到Eureka
@EnableEurekaClient
public class ConfigServerApplication {

public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
访问测试

在浏览器输入如下地址可以访问到配置文件的信息

访问http://localhost:8899/application-dev.properties

其他的访问方式

官网上介绍了如下几种访问方式:

1
2
3
4
5
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

其中第一种方式返回的是 json 数据(如下图所示),其它方式返回的都是文件真正的内容

规则详情
  1. 第一个规则的分支名是可以省略的,默认是master分支
  2. 无论你的配置文件是properties,还是yml,只要是应用名+环境名能匹配到这个配置文件,那么就能取到
  3. 如果是想直接定位到没有写环境名的默认配置,那么就可以使用default去匹配没有环境名的配置文件
  4. 使用第一个规则会匹配到默认配置
  5. 如果直接使用应用名来匹配,会出现404错误,此时可以加上分支名匹配到默认配置文件
  6. 如果配置文件的命名很由多个-分隔,此时直接使用这个文件名去匹配的话,会出现直接将内容以源配置文件内容直接返回,内容前可能会有默认配置文件的内容(已测试)

客户端准备

导入POM文件
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
添加bootstrap配置文件

添加 bootstrap.properties

1
2
3
4
5
6
7
8
9
10
#注册中心地址
spring.cloud.config.uri=http://localhost:8899
#配置文件分支
spring.cloud.config.label=master
# 配置文件名称
spring.cloud.config.name=application
# 配置文件环境
spring.cloud.config.profile=dev
#如果连接不上获取配置有问题,快速响应失败
spring.cloud.config.fail-fast=true
修改application配置

修改application.properties配置文件使用注册中心的 eureka地址以及配置

1
2
3
4
5
6
7
8
9
10
11
server.port=8081
spring.application.name=user-server

eureka.client.serviceUrl.defaultZone=${eureka.client.serviceUrl.defaultZone}
# 续约更新时间间隔,一般设置比续约到期时间少,该配置表示,每隔30秒就向服务端发送心跳。
eureka.lease-renewal-interval-in-seconds=${eureka.lease-renewal-interval-in-seconds}
# 续约到期时间,可以单独给每个服务设置,如果在90秒(默认)内没有给服务发送心跳,则剔除该服务。
eureka.instance.lease-expiration-duration-in-seconds=${eureka.lease-renewal-interval-in-seconds}
# 每隔30秒就去注册中心拉取注册表信息。
eureka.client.registry-fetch-interval-seconds=${eureka.lease-renewal-interval-in-seconds}
...
编写Controller

编写 controller,获取配置中心中的文件属性

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class UserController {
//从配置中心获取order.version的值
@Value("${order.version}")
private String orderVersion;


@GetMapping("/order/version")
public String getOrderVersion() {
System.out.println(orderVersion);
return orderVersion;
}

启动服务

启动服务发现启动后会首先从配置中心拉取数据

访问测试

访问http://localhost:8081/order/version 进行测试

如果需要获取其它配置文件内容,只需要修改 bootstrap.yml 中的 labelnameprofile 即可

动态刷新

场景

将github的 order.version的值由1.0.0改为1.1.0并提交

访问测试

不重启访问测试

客户端修改

导入POM

需要引入 actuator 依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
暴露监控端点

主要开启refresh的监控端点

1
2
# 开启 health以及hystrix.stream端点的监控
management.endpoints.web.exposure.include=refresh,info,health,hystrix.stream
Controller 增加注解

在 Controller 上添加注解 @RefreshScope

1
2
3
4
5
6
@RestController
//自动刷新注解
@RefreshScope
public class UserController {
.....
}
发送更新请求

刷新服务端后,发送 Post 请求,curl -X POST http://localhost:8081/actuator/refresh,客户端刷新即可获取最新内容,避免了服务重启

我们发现客户端后端开始进行更新

请求测试

仍存在的问题

  • 每个微服务都需要发送一次 POST 请求。
  • 如何广播通知?一次通知,处处生效
  • 如何进行差异化的处理,让部分服务动态获取

加解密

​ 在微服务架构中,由于独立的服务个数众多,加上前期测试工作量大,一些原本由运维人员维护的敏感信息会被我们直接写在微服务中,以提高开发效率,但是这种明文存储方式显然是非常危险的,所以我们要对这些信息进行加密,而Spring Cloud Config则提供了对称加解密、非对称加解密的功能来帮助我们完成这一需求。OK,本文我们就来看看如何实现配置信息的加解密。

准备工作

​ 默认情况下我们的JRE中自带了JCE(Java Cryptography Extension),但是默认是一个有限长度的版本,我们这里需要一个不限长度的JCE,这个JCE我们可以直接百度然后在Oracle官网下载,下载之后解压,我们可以看到如下三个文件:

我们需要将这里的两个jar包拷贝到我们的jdk安装目录下,我的是%JAVA_HOME%\jre\lib\security,覆盖该目录下原有的文件。

如此之后,我们的准备工作就完成了。

对称加解密

配置

​ 对称加解密比较简单,直接配置密钥就可以了,在我们前文创建出来的config-server中配置密钥,但是注意这个密钥需要配置在bootstrap.properties中,另外这里还有非常重要一点:Spring Cloud的Dalston.SR3和Dalston.SR2版本在这个问题上是有BUG的,如果用这两个版本在这里测试会没有效果,应该避开使用这两个版本,我这里使用的是Dalston.SR4版本,配置如下:

1
encrypt.key=123456abdcf
测试

配置完成之后,启动我们的config-server工程,然后访问如下地址,如果看到如下访问结果,表示环境搭建成功了:

生成密文

此时我们就可以通过第三方工具如POSTMAN、RestClient或者命令等来访问/encrypt和/decrypt接口,比如说我要给dev这个字符加密,方式如下(我这里以POSTMAN为例,注意是POST请求):

1
http://localhost:8899/encrypt

解密密文
1
http://localhost:8899/decrypt

配置密文

OK,拿到加密的字符串之后,到我们的github配置文件中修改

小伙伴们注意,配置文件的值如果是以{cipher}开头,表示该值是一个加密字符,配置中心config-server在获取到这个值之后会先对值进行解密,解密之后才会返回给客户端使用。

1
order.version={cipher}909acdbc9f5a2a6c887a4bf421e1fdd24dc63d697be091246190f1c0bc254ed4

测试

先更新客户端然后请求

测试成功

非对称加解密

上文我们使用了对称加解密的方式来确保配置文件的安全性,如果使用非对称加解密的方式,我们的安全性将会得到进一步的提高。

生成密钥

使用非对称加密的话需要我们先生成密钥对,生成密钥对可以直接使用jdk中自带的keytool工具,方式如下:

1
keytool -genkeypair -alias config-server -keyalg RSA -keystore config-server.keystore

执行效果如图:

配置密钥

执行成功之后,会在命令执行目录下生成一个名为config-server.keystore的文件,将该文件拷贝到config-server的src/main/resources目录下,然后在bootstrap.properties配置文件中如下配置。

1
2
3
4
encrypt.key-store.location=config-server.keystore
encrypt.key-store.alias=config-server
encrypt.key-store.password=123456
encrypt.key-store.secret=123456
生成并校验密文

参考对称加密的生成以及解密密文

加密

解密

配置密文

到我们的github配置文件中修改

测试

先更新客户端然后请求

评论