1. 介绍
分布式微服务系统面临的问题
微服务意味着要将单体应用中的业务拆分成一个个子服务, 每个服务的粒度相对较小,因此系统中会出现大量的服务, 由于每个服务都需要必要的配置信息才能运行, 所以一套集中式动态的 配置管理设施是必不可少的,
例如:
- 不同环境,不同配置。例如,数据源配置在不同的环境(开发、测试、预发布、生产等)中是不同的
- 运行期间可动态调整。例如,我们可根据各个微服务的负载情况,动态调整数据源连接池大小或熔断阈值,并且在调整配置时不停止微服务
- 配置修改后可自动更新。如配置内容发生变化,微服务能够自动更新配置。
而Spring Cloud 就可以顺利解决这个问题,
官方文档: https://docs.spring.io/spring-cloud-config/docs/2.2.5.RELEASE/reference/html/
SpringConfig 简介
Spring Cloud Config为分布式系统外部化配置提供了服务器端和客户端的支持,它包括Config Server和Config Client两部分。
Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环 境下的配置,默认使用Git存储配置内容(也可使用Subversion、本地文件系统或Vault存储 配置),因此可以方便的实现对配置的版本控制与内容审计。 Config Client 是Config Server的客户端,在启动或刷新时读取存储在Config Server中的配置属性,
2. 基本使用
2.1 SpringConfig Server 搭建
Server端也是作为Eureka的Clinet注册进注册中心,
同时他也需要依赖一个配置文件的管理地址,这里我使用gitee,
首先我们需要创建一个仓库,并将本地创建好的不同环境的配置文件写好(必须是UTF-8),并上传到仓库中,入图所示
内容分别是:
-
config-dev.yml
config: info: "master branch,springcloud-config/config-dev.yml version=121"
-
config-prod.yml
config: info: "master branch,springcloud-config/config-prod.yml version=8"
-
config-test.yml
config: info: "master branch,springcloud-config/config-test.yml version=8"
下面开始搭建服务端
pom文件依赖:
<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-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Yaml配置文件
server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: https://gitee.com/xxxx/springcloud-config.git # 你的git项目地址
username: username #如果仓库为私有的则需要配置账号密码
password: password
label: master #默认master分支
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
主启动类:
@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class, args);
}
}
启动成功后,测试是否可以访问git, 访问地址:http://127.0.0.1:3344/master/config-dev.yml
,成功访问git中对应配置文件信息
访问规则
在上面的测试中,我们使用一个链接,访问到了 配置文件信息, SpringConfig 中提供了多种方式,下面列举常用的几种
- 通过{application}-{profiles}.yml来访问
http://127.0.0.1:3344/config-dev.yml
http://127.0.0.1:3344/config-test.yml
http://127.0.0.1:3344/config-prod.yml
- 通过/{application}-{profiles}/{lable}
http://127.0.0.1:3344/config-test/marter
http://127.0.0.1:3344/config-dev/marter
http://127.0.0.1:3344/config-prod/marter
- 通过/lable/application-{profiles}.yml (推荐)
http://127.0.0.1:3344/master/config-dev.yml
http://127.0.0.1:3344/master/config-test.yml
http://127.0.0.1:3344/master/config-prod.yml
2.2 SpringConfig Clinet 搭建
client 端用于读取 Server中的配置信息,一般为普通的业务微服务
Pom依赖: 和Server端没有区别
bootstrap.yml
因为项目中的配置信息,需要从Config Server 中读取,用来初始化项目中的各个模块,那么Config Server 地址等信息应该在bootstrap.yml
中配置,
-
此配置文件为引导类配置文件, 优先级最高,并且是在项目启动初始化前就已经被加载,,Spring Cloud 会创建一个 "Bootstrap Context",作为Spring应用的 "Application Context" 的父上下文, 初始化的时候, "Bootstrap Context" 负责从外部源加载配置属性并解析配置, 这两个上下文共享一个外部获取的"Environment"
-
Bootstrap 属性 有高优先级,默认情况下,他们不会被本地配置覆盖, "Bootstrap Context" 和 "Application Context" 有着不同的约定,所以新增一个bootstrap.yml 文件, 保证"Bootstrap Context" 和 "Application Context" 配置分离
所以将Config Server的配置信息 配置在这里才可以被读取
bootstrap.yml信息(放在resources根目录下即可)
server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
label: master # 分支名
name: config # 配置文件名称
profile: dev # 环境后缀名称
uri: http://localhost:3344 # 综上所述, 最终读取的文件为 http://localhost:3344/master/config-dev.yml
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
测试controller,获取从Server端读取的配置信息
@RestController
@RefreshScope //手动刷新
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
启动类
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class, args);
}
}
测试:
浏览器访问接口: http://127.0.0.1:3355/configInfo
,返回读取的信息master branch,springcloud-config/config-dev.yml version=121
3. Config客户端动态刷新
此时,当我们模拟运维人员,对git中的配置文件进行修改,并提交
-
刷新3344,Server端, 发现 Config Server 配置中心立刻响应
-
刷新3355,Clinet端, 发现 Config Server 配置中心没有刷新
那么,如果修改了配置文件,我们就需要将所有的客户端都进行重启加载,这在实际生产环境中,几乎是不可能的事情,所以我们需要使客户端自动刷新
修改Pom文件,添加额外的依赖(actuator包的功能可以自行百度学习,这里使用了它动态加载配置文件的功能)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
开启refresh的监控端口
management:
endpoints:
web:
exposure:
include: "*" #开启所有的端点
在读取配置文件的类上加上@RefreshScope
注解
@RefreshScope
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
测试:
修改dev文件 version改为 130
config:
info: "master branch,springcloud-config/config-dev.yml version=130"
再调用client端 接口http://127.0.0.1:3355/actuator/refresh
(使用post请求)刷新配置信息, (调用此接口,使用actuator的功能,会清除注解@RefreshScope
的缓存以及@ConfigurationProperties
重新加载,也就是将用户自定义使用到的配置文件和Spring Boot项目中各种自动配置的信息都刷新一遍)
再读取client端的信息: http://127.0.0.1:3355/configInfo
, 发现已经更新master branch,springcloud-config/config-dev.yml version=130
4. config bus 消息总线支持
在上一节中,我们解决了 修改配置文件,需要重启项目的问题,但是如果微服务过多,那么每次修改配置文件,大量的微服务都需要手动调用请求进行刷新,也是一个比较烦人的工作, 也可以对此进行优化
方式一:
可以利用消息队列的通知方式, 设定一个消息总线方,一般就为 Config Server 端, 设定一个Http 接口, 接口中向指定的队列中发送广播信号, 那么所有Config Server 端只需监听这个队列, 当接受到更新信息后,只需再代码中调用本服务的 Post 刷新接口即可,就可刷新本微服务的配置信息,
如此,当我们修改完配置后,只需调用总线的Http接口,就可自动更新所有Clinet端配置信息
方法二:
使用 消息总线 bus来实现
官方文档:https://spring.io/projects/spring-cloud-bus
原理和上述方案一大致相同, 所以需要依赖 MQ 中间件, 目前仅仅支持 RabbitMq 和 Kafka,下面使用 Rabbitmq演示, 为了演示复杂性,再复制一份客户端, 端口为 3366
Config Server端 和所有需要 Config Clinet 端 添加 相关依赖
<!-- 添加消息总线RabbitMQ支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
同时也都需要配置 RabbitMq的账号地址等信息
server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: https://gitee.com/xxxxx/springcloud-config.git
username: username
password: password
label: master #默认master分支
rabbitmq:
virtual-host: /test
host: dev.rabbitmq.md.com
port: 5672
username: username
password: password
但是此时,我们刷新的端点将不是Clinet本身,而是通过 Server端 刷新,并通知给所有客户端,所以Server也要依赖 actuator ,并开放端点
修改Server端配置yml
# 消息总线的支持 这个就是总线 只刷新这个
management:
endpoints:
web:
exposure:
include: "*" # 开放所有,也可以仅仅开放消息总线的支持 bus-refresh
测试:
修改git中的文件, 并调用 Server 端刷新消息总线接口:http://127.0.0.1:3344/actuator/bus-refresh
(Post方式),
这时候我们发现, Clinet端 无须任何操作,配置文件就已经刷新 可以调用接口查询:http://127.0.0.1:3355/configInfo
,说明消息总线配置成功,
通过查看RabbitMq的 后台管理页面,可以看见, 在Rabbitmq中自动创建了一个交换机 springCloudBus ,用于通知
单独通知某一个微服务
在上面的案例中,我们刷新后,所有监听mq的客户端都会刷新配置文件,但是当我们想要单独刷新某一个客户端微服务时,也可以使用如下的方式
调用Server端总线接口刷新: http://127.0.0.1:3344/actuator/bus-refresh/{destination}
,并指定某一个微服务,
例如: http://127.0.0.1:3344/actuator/bus-refresh/config-client:3355
, (微服务名:端口的方式), 可以单独刷新某一个微服务