Spring Cloud Config 介绍
Spring Cloud Config 是一个分布式配置中心。
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
Spring Cloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/
Spring Cloud Config分为服务端和客户端两部分。
Config 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。
Config 客户端则是通过指定的配置中心(Config 服务端)来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
Config 客户端是不会主动去 Config 服务端更新配置信息的,而只会在等待运维人员发送 refresh 请求时才会去 Config 服务端请求更新配置信息(或配置消息总线后从消息队列中获取最新配置信息)。
Config 作用
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署比如
dev/test/prod/beta/release
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
- 将配置信息以REST接口的形式暴露 -
post/crul
访问刷新即可
与GitHub整合配置
由于Spring Cloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https
访问的形式。
Config 配置总控中心搭建
创建远程仓库
在Github上创建仓库 springcloud-config
,其内存储三个不同环境的配置文件
1 2
| config: info: "master branch,springcloud-config/config-dev.yml version=1"
|
1 2
| config: info: "master branch,springcloud-config/config-prod.yml version=1"
|
1 2
| config: info: "master branch,springcloud-config/config-test.yml version=1"
|
创建配置中心模块
新建Module模块cloud-config-center-3344
,它即为配置中心模块Cloud Config Center。
- 导入Maven依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <?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>cloud2021</artifactId> <groupId>com.zhao.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-center-3344</artifactId>
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties>
<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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
</project>
|
- 配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| server: port: 3344
spring: application: name: cloud-config-center cloud: config: server: git: uri: https://github.com/YUYUN-ZHAO/springcloud-config.git search-paths: - springcloud-config username: YUYUN-ZHAO password: zhaoyuyun123. label: master
eureka: client: service-url: defaultZone: http://localhost:7001/eureka
|
- 主启动类
1 2 3 4 5 6 7 8 9 10 11 12
| import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer public class ConfigCenterMain3344 public static void main(String[] args) { SpringApplication.run(ConfigCenterMain3344.class, args); } }
|
- 测试:
1 2
| config: info: "master branch,springcloud-config/config-dev.yml version=7"
|
上文中的配置读取规则见官方文档。常用规则:/{label}/{application}-{profile}.yml
例如/master/config-dev.yml
对应了配置中心仓库里的config-test.yaml
文件:
Config 客户端搭建
Config 客户端通过 Config 服务端获取 Github 配置中心仓库里的配置文件。
- 创建模块
cloud-config-client-3355
- 导入Maven依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <?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>cloud2021</artifactId> <groupId>com.zhao.springcloud</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-3355</artifactId>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
|
- 添加总配置文件
bootstrap.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| server: port: 3355
spring: application: name: config-client cloud: config: label: master name: config profile: dev uri: http://localhost:3344
eureka: client: service-url: defaultZone: http://localhost:7001/eureka
|
该配置文件中配置的信息组合起来即为访问http://config-3344.com:3344/master/config-dev.yml,并通过 Config 服务端获取到配置中心仓库里的config-dev.yaml
文件。
说明:
applicaiton.yml
是用户级的资源配置项
bootstrap.yml
是系统级的配置资源,优先级更加高
Spring Cloud会创建一个BootstrapContext,作为Spring应用的ApplicationContext的父上下文。初始化的时候,BootstrapContext负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。
Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。BootstrapContext和ApplicationContext有着不同的约定,所以新增了一个bootstrap.yml
文件,保证BootstrapContext和ApplicationContext配置的分离。
要将Client模块下的application.yml
文件改为bootstrap.yml
,这是很关键的,因为bootstrap.yml
是比application.yml
先加载的。bootstrap.yml
优先级高于application.yml
。
- 修改
config-dev.yml
配置并提交到GitHub中,比如加个变量age
或者版本号version
- 主启动类:
1 2 3 4 5 6 7 8 9 10 11
| import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient @SpringBootApplication public class ConfigClientMain3355 { public static void main(String[] args) { SpringApplication.run(ConfigClientMain3355.class, args); } }
|
- 业务类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController @RefreshScope public class ConfigClientController { @Value("${config.info}") private String configInfo;
@GetMapping("/configInfo") public String getConfigInfo() { return configInfo; } }
|
- 测试:启动3355作为Client访问 http://localhost:3355/configlnfo
成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息
但此时问题在于:每次配置中心仓库的数据修改后,Config 客户端并不会被立即通知,需要重启服务才可以得到最新配置信息。解决方案:
- 通过
curl
等命令手动刷新
- 配置 Bus 消息总线
手动刷新 Config 配置
动态刷新步骤:
- 引入
actuator
监控依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
- 修改YML,添加暴露监控端口配置:
1 2 3 4 5 6
| management: endpoints: web: exposure: include: "*"
|
- 使用 @RefreshScope 注解修饰业务类 Controller,使得其能动态刷新配置文件:
1 2 3 4 5 6 7
| import org.springframework.cloud.context.config.annotation.RefreshScope;
@RestController @RefreshScope public class ConfigClientController { }
|
- 手动发送命令刷新 Config 客户端:
1
| $ curl -X POST "http://localhost:3355/actuator/refresh"
|
Spring Cloud Bus 介绍
Config 客户端是不会主动去 Config 服务端更新配置信息的,而只会在等待运维人员发送 refresh 请求时才会去 Config 服务端请求更新配置信息,此时使用 Spring Cloud Bus 配置消息总线即可实现配置信息的自动刷新。
Spring Cloud Bus 是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。Spring Clud Bus 目前支持 RabbitMQ 和 Kafka。
Spring Cloud Bus 能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。下图中黄色区域即为消息总线,本质上是一个消息队列,当收到更新配置的消息后通知所有的 Config 客户端更新最新配置:
基本原理
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
所有ConfigClient实例都监听MQ中同一个topic(在RabbitMQ中为某个交换机)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。
工作流程
- 配置中心 Config Server 从GitHub仓库中读取配置信息保;向RabbitMQ服务端注册一个交换机,其他 Config Client 注册的消息队列都将绑定这个交换机;
- 所有 Config Client 从 Config Server 拉取到配置信息;每个 Config Client 都将创建一个消息队列,与 Config Server 注册的交换机进行绑定,并一直监听其刷新消息
- 运维人员修改GitHub仓库里的配置消息
- 发送 Post 请求到 Config Server,通知其配置信息需要更新
- Config Server 作为消息生产者发送刷新消息到RabbitMQ服务端的交换机
- 交换机将该消息发送到对应的队列中(默认是发送到所有消息队列,若设置了目标客户端的名称规则,则只会发送到对应的队列中)
- 发现消息队列中出现刷新事件的 Config Client 将主动拉去 Config Server 中的配置信息,从而完成自动更新配置信息
Bus 之 RabbitMQ 环境配置
新建 cloud-config-client-3366
- 导入 Maven 依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <?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>LearnCloud</artifactId> <groupId>com.lun.springcloud</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-3366</artifactId>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
|
- 配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| server: port: 3366
spring: application: name: config-client cloud: config: label: master name: config profile: dev uri: http://localhost:3344
rabbitmq: host: localhost port: 5672 username: guest password: guest
eureka: client: service-url: defaultZone: http://localhost:7001/eureka
management: endpoints: web: exposure: include: "*"
|
- 主启动类:
1 2 3 4 5 6 7 8 9 10 11
| import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient @SpringBootApplication public class ConfigClientMain3366 { public static void main(String[] args) { SpringApplication.run(ConfigClientMain3366.class,args); } }
|
- Controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController @RefreshScope public class ConfigClientController { @Value("${server.port}") private String serverPort;
@Value("${config.info}") private String configInfo;
@GetMapping("/configInfo") public String configInfo() { return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo; } }
|
Bus 架构选型
架构思想一
利用消息总线触发一个客户端 /bus/refresh
,而刷新所有客户端的配置:
架构思想二
利用消息总线触发一个服务端 ConfigServer 的 /bus/refresh
端点,而刷新所有客户端的配置:
架构思想二的架构显然更加适合,架构思想—不适合的原因如下:
- 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
- 破坏了微服务各节点的对等性。
- 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改。
下文将介绍架构思想二下的案例。
Bus 动态刷新全局广播配置实现
给上文中构建的 cloud-config-center-3344
配置中心服务端 Config Server 添加消息总线支持。
- 导入 Maven 依赖:
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amap</artifactId> </dependency> <dependency> <groupId>org-springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
- 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| server: port: 3344
spring: application: name: cloud-config-center cloud: config: server: git: uri: https://github.com/YUYUN-ZHAO/springcloud-config.git search-paths: - springcloud-config username: YUYUN-ZHAO password: zhaoyuyun123. label: master
rabbitmq: host: localhost port: 5672 username: guest password: guest
eureka: client: service-url: defaultZone: http://localhost:7001/eureka
management: endpoints: web: exposure: include: 'bus-refresh'
|
给cloud-config-client-3355
客户端添加消息总线支持。
- 导入 Maven 依赖:
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amap</artifactId> </dependency> <dependency> <groupId>org-springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
- 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| server: port: 3355
spring: application: name: config-client cloud: config: label: master name: config profile: dev uri: http://localhost:3344
rabbitmq: host: localhost port: 5672 username: guest password: guest
eureka: client: service-url: defaultZone: http://localhost:7001/eureka
management: endpoints: web: exposure: include: "*"
|
添加了消息总线和RabbitMQ配置后,再修改Github上配置文件内容,只需要给 Config Server 发送一次刷新消息即可实现所有Config客户端更新消息:
1
| curl -X POST "http://localhost:3344/actuator/bus-refresh"
|
Bus 动态刷新定点通知
若只想通知部分Config Client更新消息,只需在发送刷新消息时遵循规则:http://localhost:3344/actuator/bus-refresh/{destination}
此时 /bus/refresh
请求不再发送到具体的服务实例上,而是发给Config sServer通过destination
参数类指定需要更新配置的服务或实例,示例:
1
| curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355
|
其代表只发给端口号为3355的应用名为 config-client
的配置客户端。
总结: