Spring Cloud Config 分布式配置中心与 Spring Cloud Bus 消息总线的使用与配置实现
Spring Cloud Config 分布式配置中心
一、什么是 Config 分布式配置中心
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/
Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
SpringCloud Config分为服务端和客户端两部分:
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。
客户端(也就是服务消费端)则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心(Config Server 服务端)获取和加载配置信息配置服务器默认采用 git 来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过 git 客户端工具来方便的管理和访问配置内容。
二、为啥会出现 Config 配置中心
微服务意味着要将单体应用中的业务拆分成一个个子服务,此时系统中就会出现大量的服务,由于每个服务都需要必要的配置信息才能运行,如果每一个服务都弄自己的配置例如数据库连接:如果是服务提供者集群中的每一台服务都需要连接数据源,则每一台服务提供者都需要在 yml 配置文件中配置数据源,这样就会产生配置信息冗余,所以一套的集中式的、动态的配置管理是必不可少的。
三、使用 Spring Cloud Config 的好处
1、在不同的环境(分环境部署如:dev开发环境、test测试环境、prod生产环境等)不同的配置中,动态化的配置更新。
2、运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息。
3、当远程仓库里的配置发生变化时,服务器不需要再重启即可感知到配置的变化并应用新的配置。
4、将配置信息以 REST 接口的形式暴露:post、curl 命令均可以刷新访问。
四、Config 服务端配置与测试
Config 服务端的作用:就是将远程 Git 或者 Gitee 仓库的配置读取到分布式配置中心以供客户端统一拉取。
配置前准备:
1、首先在 GitHub 或者 Gitee上新建一个名为 springcloud-config 的远程仓库,(我这里以 Gitee 为例)详细步骤见 https://blog.csdn.net/tf835991342/article/details/119927409 中的第六步:码云的使用
2、在本地新建一个文件夹,并创建以下 三个 yml 配置文件:
config-dev.yml
config:
info: "master branch,springcloud-config/config-dev.yml version=1"
config-prod.yml
config:
info: "master branch,springcloud-config/config-prod.yml version=1"
config-test.yml
config:
info: "master branch,springcloud-config/config-test.yml version=1"
3、随后执行以下命令,将本地配置文件上传到远程仓库里:
# 首先如果 Gitee 远程仓库初始化过,会生成 README.en.md 和 README.md 文件则可直接执行以下命令先克隆远程仓库的初始化文件,再顺序执行第2、3、6步骤
git clone 仓库的远程地址(如:https://gitee.com/aFeiStudy/springcloud-config.git)
# 1、(先进入项目文件夹)通过命令 git init 把这个目录变成git可以管理的仓库(如果执行过 git clone 命令可省略)
git init
# 2、把文件添加到版本库中,使用命令 git add .添加到暂存区里面去,不要忘记后面的小数点“.”,意为添加文件夹下的所有文件
git add .
3、用命令 git commit告诉Git,把文件提交到仓库。引号内为提交说明
git commit -m 'first commit'
# 4、关联到远程库(如果执行过 git clone 命令可省略)
git remote add origin 你的远程库地址
如:
git remote add origin https://github.com/cade8800/ionic-demo.git
# 5、获取远程库与本地同步合并(如果远程库不为空必须做这一步,否则后面的提交会失败,我这里是新建的所以可以省略此步)
git pull --rebase origin master
# 6、把本地库的内容推送到远程,使用以下命令,实际上是把当前分支master推送到远程。执行此命令后会要求输入用户名、密码,验证通过后即开始上传。
git push
# 7、如果正常push失败,可以用这个命令强制覆盖分支:(如果远程仓库为空可省略,我这里是新建的所以可以省略此步骤)
git push -u origin master -f
# 8、下载所有远程库的变化(如果远程仓库为空可省略,我这里是新建的所以可以省略此步骤)
git fetch
# 9、将下载的内容与本地库合并(如果远程仓库为空可省略,我这里是新建的所以可以省略此步骤)
git merge origin/master
随后 Gitee 远程仓库会出现以下内容:
至此,配置前准备已完成。
Config 服务端配置如下(以读取 gitee 仓库中的配置文件为例,GitHub 同理):
-
首先新建一个 模块工程
cloud-config-center-3344
-
编写 pom.xml 配置文件
<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>
-
编写 application.yml 配置文件
server: port: 3344 spring: application: name: cloud-config-center #注册进Eureka服务器的微服务名 cloud: config: server: git: uri: https://gitee.com/aFeiStudy/springcloud-config.git # Gitee 上面的远程克隆/下载 https 地址 username: 登录 Gitee 的用户名 password: 登录 Gitee 的密码 #### 搜索目录 search-paths: - springcloud-config #### 读取分支 label: master # 服务注册到eureka地址 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 注册中心集群
-
编写主启动类 ConfigCenterMain3344.java
/** * @Author Herz * @Date 2022/1/14 18:08 */ @SpringBootApplication // 开启 spring cloud config 分布式配置中心 @EnableConfigServer @EnableEurekaClient public class ConfigCenterMain3344 { public static void main(String[] args) { SpringApplication.run(ConfigCenterMain3344.class, args); } }
-
启动主启动类,进行测试:输入地址
http://localhost:3344/master/config-dev.yml
测试,其他两个 yml 配置文件读取地址类似
成功实现用 Spring Cloud Config 服务端通过 Gitee 获取配置信息。
补充:配置文件的读取规则
其中:application:应用程序名(即配置文件名的前缀 - 之前的名称),label:分支名,profiles:环境(dev/test/prod)
以图中的三个配置文件为例:
各种配置读取规则(即路径)例子如下:
- /{application}/{profile}[/{label}]
- 例如:http://localhost:3344/config/dev/master,这里返回的是 JSON 数据
- /{application}-{profile}.yml
- 例如:http://localhost:3344/config-dev.yml
- /{label}/{application}-{profile}.yml
- 例如:http://localhost:3344/master/config-dev.yml
- /{application}-{profile}.properties
- 例如:http://localhost:3344/config-dev.properties
- /{label}/{application}-{profile}.properties
- 例如:http://localhost:3344/master/config-dev.properties
五、Config 客户端配置与测试
config 客户端的作用:当收到通知时,重新到 config 服务端拉取最新的配置信息。
1、创建一个微服务模块:cloud-config-client-3355
2、编写 pom.xml 依赖
<dependencies>
<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>
3、编写 bootstrap.yml配置文件
server:
port: 3355
spring:
application:
name: cloud-config-client
cloud:
# Config客户端配置
config:
label: master # 分支名称
name: config # 配置文件名称
profile: dev # 读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://localhost:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
# 服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 注册中心集群
补充:关于 bootstrap.yml
applicaiton.yml 是用户级的资源配置项;bootstrap.yml是系统级的,优先级更加高。
Spring Cloud 会创建一个 “Bootstrap Context”,作为Spring应用的 Application Context
的父上下文。初始化的时候,Bootstrap Context
负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的 Environment
。
Bootstrap
属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap context
和 Application Context
有着不同的约定,所以新增了一个 bootstrap.yml
文件,保证 Bootstrap Context
和 Application Context
配置的分离。
4、测试 config客户端 是否能够 通过 config服务端 来获取 Gitee 远程仓库的配置信息:在浏览器输入:http://localhost:3355/configInfo
上图说明:config 客户端已成功通过 config 服务端获取 Gitee 远程仓库的配置信息。
5、现在遇到了一个大问题:如果现在在远程仓库里改动某个配置文件的配置信息,例如 config-dev.yml 的版本信息,改为第 2 版:
那么 config 服务端可以立即读到配置信息的改动:
但是 config客户端 无法立即获取修改后的配置信息,只能重启后才能获取最新的配置信息。
如何解决呢???这就涉及到了config 客户端的动态刷新。
六、config 客户端之动态刷新——手动版
1、在 pom.xml 中引入 actuator 监控(一般除了注册中心微服务不添加该依赖,其他微服务都需要添加)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、修改 yml,暴露监控端口
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
3、在业务类 Controller 中添加注解 @RefreshScope
/**
* @Author Herz
* @Date 2022/1/17 14:40
*/
@RestController
@RefreshScope
public class ConfigClientController {
// 通过 config server 配置中心端获取配置信息
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
4、重启 config 客户端微服务,再次修改 config-dev.yml 的版本信息,改为第 3 版。
测试 config 客户端是否能够立马读取到最新的配置信息:
可见还是没有读取到最新的配置信息,为啥呢???因为每一次配置信息的更新需要手动去发一个 POST 请求通知 config 客户端刷新拉取最新的配置信息。这里通过 curl 命令模拟发送通知:curl -X POST "http://localhost:3355/actuator/refresh"
再次刷新页面,可以发现 config 客户端已经读取到了最新的配置信息。
难道每次修改配置都需要手动发送 POST请求通知 config 客户端嘛以及能否定点通知???虽然config 客户端微服务多了之后可以通过脚本执行,但是还是很麻烦,那有没有一次性通知到全部即广播呢???是有的,这就需要用到 Bus 消息总线的全局广播。同时还可以使用 Bus 消息总线实现定点通知。
Spring Cloud Bus 消息总线
一、什么是 Spring Cloud Bus 消息总线
Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。
Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。
Spring Clud Bus目前支持 RabbitMQ 和 Kafka。
二、Spring Cloud Bus 能干啥
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。
三、为何被称为总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上实例都知道的消息。
基本原理:Config Client 实例都监听 MQ 中的统一个 Topic(默认是 Spring Cloud Bus)。当一个服务刷新数据的时候,它会把这个信息放入到 Topic 中,这样其它监听同一 Topic 的服务就能得到通知,然后去更新自身的配置。
四、Spring Cloud Bus 动态刷新全局广播——手动版
做配置前的准备:为了演示广播效果,需要以 cloud-config-client-3355 模块(在以上Config 客户端配置与测试中创建的一个服务模块)为模板再创建一个 cloud-config-client-3366。
1、全局广播的设计思想
-
1)利用消息总线触发一个 config 客户端 /bus/refresh,而刷新所有客户端的配置。
-
2)利用消息总线触发 config 服务端 /bus/refresh端点,而刷新所有客户端的配置
相比之下,显然第二种方案更加适合,第一种方案不合适的原因如下:
① 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责;
② 破坏了微服务各节点的对等性;
③ 有一定的局限性。例如:微服务在迁移的时候,它的网络地址尝尝会发生变化,此时如果想要做到自动刷新,就需要修改很多东西。
2、动态刷新全局广播的配置实现
1)给 config 配置中心服务端 cloud-config-center-3344 添加消息总线支持
在 pom.xml 配置文件中加入消息总线RabbitMQ依赖:
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
在 application.yml 配置文件中加入以下配置(注意 rabbitmq 的相关的配置是在 spring 下面):
# rabbitmq相关配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
## rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: # 暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
2)给 config 客户端 cloud-config-center-3355 添加消息总线支持
在 pom.xml 配置文件中加入消息总线RabbitMQ依赖:
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
在 bootstrap.yml 配置文件中加入以下配置(注意 rabbitmq 的相关的配置是在 spring 下面):
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
3)给 config 客户端 cloud-config-center-3366 添加消息总线支持
在 pom.xml 配置文件中加入消息总线RabbitMQ依赖:
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
在 bootstrap.yml 配置文件中加入以下配置(注意 rabbitmq 的相关的配置是在 spring 下面):
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
4)修改 config-dev.yml 的版本信息,将原先的第3 版改为第 4 版,
模拟运维人员通过 curl 命令给 config 配置中心服务端发送 POST 请求 curl -X POST "http://localhost:3344/actuator/bus-refresh"
测试 config 客户端是否能够立马读取到最新的配置信息:
同时 RabbitMQ 里也会生成共享的 topic springCloudBus
那么除了统一的广播通知,能不能指通知指定的某个 config 客户端呢??当然是可以的!那就要用到 动态刷新定点通知。
3、动态刷新定点通知
定点通知:即只通知指定的某个 config 客户端
curl 命令发送 POST 请求格式:curl -X POST "http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}"
说明:/bus/refresh 请求不再发到所有具体的服务实例上,而是发给 config server 并通过 destination参数指定于需要更新配置的服务实例。(destination 的参数格式:微服务名 : 端口号
)
下面以 只 通知 3355 端口的 config 客户端为例:
当 Gitee 远程仓库的配置文件内容发生改变时,
curl 命令发送 POST 请求:curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"