Spring Cloud Bus 消息总线
简介
Spring Cloud Bus 将轻量级消息代理程序链接到分布式系统的节点。然后可以使用此代理来广播状态更改(例如配置更改)或其他管理指令。一个关键的想法是,总线就像是横向扩展的 Spring Boot 应用程序的分布式执行器。但是,它也可以用作应用之间的通信渠道。该项目为 AMQP 经纪人或 Kafka 提供了入门服务。
官网: https://cloud.spring.io/spring-cloud-bus/reference/html/
Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新!
Spring Cloud Bus 支持两种消息代理:RabbinMQ 和 Kafka
根据上图可知,在某个时候改变了 git 仓库中的某个数据之后,3344 会直接去获得最新数据,然后再由一个中间件给我们自动的提交命令来进行数据刷新,在之后就是由拿到最新数据的那个服务来对其他服务进行传播。
Bus能干嘛?
Spring Cloud Bus 能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当做微服务间的通信通道
这个图的效果和刚才的图片效果是不同的,刚才那个是先传播给一个服务,然后在由被传播的服务再次传播给其他服务;当这个图片是直接传给了 Config Server(3344),然后在有 Config Server 一次性传播给所有服务!
消息总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为 “消息总线”。在总线上的各个实例,都可以方便的广播一些需要让其它连接在该主题上的实例都知道的消息。
基本原理
CloudClient 实例都监听 MQ 中同一个 topic(默认是 SpringCloudBus)。 当一个服务刷新数据的时候,它会把这个信息放入到 Topic 中,这样其它监听同一 Topic 的服务就能得到通知,然后去更新自身的配置。
RabbitMQ 环境配置
Erlang 安装
下载地址: http://erlang.org/download/otp_win64_21.3.exe
基本就是傻瓜式安装,但是要注意的是安装的位置一定不要是中文目录下,否则尽可能会出现一些不必要的问题!
安装好后我们来配置环境变量(我这里配置在系统变量中,系统变量和用户变量没有太大的区别)
新建一个环境变量,名为 ERLANG_HOME,然后内容为 Erlang 的安装位置
然后在 Path 中新建两个变量,分别是 Erlang 的 bin 目录和刚才创建好的变量
RabbitMQ安裝
下载地址:http://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe
其实说白了这两个软件都是傻瓜式安装,一直下一步即可
虽然是傻瓜式安装,但是还是有一些要求的!
电脑用户名不能是中文,就算自己通过简单的方式进行修改了也是没用的,特别是用了家庭版系统的兄弟,因为家庭版一旦是中文用户名就无法在进行修改了!!!
解决方案: https://blog.csdn.net/wls666/article/details/103334152
安装目录不能有中文(这个解决方案就是把软件删除然后在放进一个英文目录下)
在 RabbitMQ 的 sbin 目录下打开命令行页面,输入以下命令启动管理功能
rabbitmq-plugins enable rabbitmq_management
启动后我们就可以在开始菜单栏看到多了以下几个东西
查看是否安装成功
点击 RabbitMQ Service - start 启动 RabbitMQ
然后在浏览器中访问 15672 端口(15672是RabbitMQ的默认端口号)
如果启动正常,那么显示的就是 RabbitMQ 的主页,然后默认的账号密码都是 guest
如果出现以下效果,那么就说明已经安装成功了
动态刷新全局广播的设计思想和选型
上面我们已经配置好了 RabbitMQ 的环境,接下来就可以开始使用了
新建一个名为 cloud-config-client-3366 的子工程
修改 pom 文件
<?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>SpringCloud2020</artifactId>
<groupId>com.lyang.springcloud</groupId>
<version>1.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-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用 Payment 支付 Entity -->
<dependency>
<groupId>com.lyang.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</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 文件
server:
port: 3355
spring:
application:
name: config-client
cloud:
# Config 客户端配置
config:
# 分支名称
label: master
# 配置文件名称
name: config
# 读取后缀名称
# 上述3个综合:master 分支上 config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yaml
profile: dev
# 配置中心地址
uri: http://localhost:3344
# 服务注册到 Eureka 地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
编写启动类
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3366 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3366.class, args);
}
}
编写业务类
/**
* @RefreshScope:帮助我们做局部的参数刷新
*/
@RestController
@RefreshScope
@Slf4j
public class ConfigClientController {
@Value("${server.port}")
private String serverPort;
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return "serverPort:" + serverPort + "\t\n\n configInfo: " + configInfo;
}
}
测试
启动顺序:7001–3344–3366
到此和之前3355没有什么区别,接下来我们在来进行调整
设计思想
利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
缺点:
① 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担刷新职责
② 破坏了微服务各个节点的对等性
③ 有一定的局限性,比如微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
利用消息总线触发一个服务端 ConfigServer 的 /bus/refresh 端点,而刷新所有客户端的配置
Bus 动态刷新全局广播配置实现
修改3344、3355、3366
在这三个服务的 pom 文件中添加以下依赖
<!-- 添加消息总线 RabbitMQ 支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
修改 3344 的 yml 文件
server:
port: 3344
spring:
application:
# 注册进 eureka 服务器的微服务名
name: cloud-config-center
cloud:
config:
server:
git:
# 自己的 github 仓库地址
uri: https://github.com/T257ymq/spring-cloud-config.git
# 搜索目录
search-paths:
- springcloud-config
# 读取分支
label: master
# RabbitMQ 相关配置-----新增部分
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 服务注册到 eureka 地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# RabbitMQ 相关配置,暴露 bus 刷新配置的端点-----新增部分
management:
# 暴露 bus 刷新配置的端点
endpoints:
web:
exposure:
include: 'bus-refresh'
修改 3355 的 yml 文件
server:
port: 3355
spring:
application:
name: config-client
cloud:
# Config 客户端配置
config:
# 分支名称
label: master
# 配置文件名称
name: config
# 读取后缀名称
# 上述3个综合:master 分支上 config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yaml
profile: dev
# 配置中心地址
uri: http://localhost:3344
# rabbitmq 相关配,15672是Web管理界面的端口,5672是 MQ 访问的端口-----新增部分
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 服务注册到 Eureka 地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
修改 3366 的 yml 文件
server:
port: 3366
spring:
application:
name: config-client
cloud:
# Config 客户端配置
config:
# 分支名称
label: master
# 配置文件名称
name: config
# 读取后缀名称
# 上述3个综合:master 分支上 config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yaml
profile: dev
# 配置中心地址
uri: http://localhost:3344
# rabbitmq 相关配,15672是Web管理界面的端口,5672是 MQ 访问的端口-----新增部分
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 服务注册到 Eureka 地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
测试
启动顺序:7001–3344–3355–3366
然后先在浏览器中测试3344、3355、3366 和 Eureka,效果如下
现在以上服务都可以正常访问,那么我们再来修改 git 仓库上 dev 中的数据
接下来就有运维工程师发送 POST 请求给 3344(客户端),由客户端一次性传播给所有的服务端
打开命令行页面输入以下命令来提交 POST 请求
curl -X POST "http://localhost:3344/actuator/bus-refresh"
此处完成后没有任何提示那么就是已经执行完了,在之前的测试当中,我们是一个服务一个服务的去进行传播的,而现在是使用客户端的方式一次性传播给所有的服务端,接下来我们再来测试一下,看看是不是所有的服务端都获取到了最新的数据!
我们可以发现,之前是对指定服务端提交 POST 请求后才可以获得最新数据,但是这样增加了很多的工作量!因为需要对每一个服务端都提交一次!
我们现在使用客户端的方式,只需要在客户端提交 POST 请求即可让所有的服务获取到最新的数据!
在之前我们在基本原理中也说过了,“ConfigClient 实例都监听 MQ 中同一个 topic(默认是 springCloudBus)”,我们可以登录 RabbitMQ 来进行查看订阅主题,只要是被订阅到的,就可以被广播通知!
就好比一个微信公众号,当很多人关注了这个公众号后,公众号发布一个内容后关注的人都可以接收到。
这个时候我们就达到了 “一次修改,广播通知,处处生效”
Bus 动态刷新定点通知
有时候我们只想通知指定的服务,而其他的服务并不通知,那么就可以使用定点通知的方式。
简单来说,就是指具体某一个实例生效而不是全部!
公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destiation}
案例
接下来就来做一个只通知 3355 而不通知 3366 的操作
不需要修改任何的代码,先修改 git 仓库中 dev 环境的版本,然后运维工程式把命令换成顶点刷新的格式即可
修改dev版本
修改后输入以下命令提交 POST 请求
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
config-client 是服务名称(对应 yml 文件中的)
测试
3344
3355
3366
由此可见,我们已经成功实现了定点刷新,只有我们指定了的服务能够获取到最新的数据,而没有被指定的服务则获取不到最新的数据