SpringCloud Bus消息总线
概述
- 上篇文章介绍了Spring Cloud Config配置中心,当我们在更新git仓库上面的配置以后,如果想要获取到最新的配置,需要手动刷新或者利用webhook的机制每次提交代码发送请求来刷新客户端,客户端越来越多的时候,需要每个客户端都执行一遍,这种方案就不太适合了。
- 使用Spring Cloud Bus可以完美解决这一问题。
总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个公用的消息主题,并让系统中的所有微服务实例都连接上来。
由于该主题中产生的消息会被所有实例监听和消费,所以称为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他链接在该主题行的实例都知道的消息。
基本原来理:ConfigClient实例都监听MQ中的同一个topic(默认是SpringcloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到topic中,这样其它监听同一个topic的服务就能得到通知,然后去更新自身的配置。
Spring Cloud Bus
Spring cloud bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其他的消息指令。
Spring bus的一个核心思想是通过分布式的启动器对spring boot应用进行扩展,也可以用来建立一个多个应用之间的通信频道。
目前唯一实现的方式是用AMQP消息代理作为通道,同样特性的设置(有些取决于通道的设置)在更多通道的文档中。
大家可以将它理解为管理和传播所有分布式项目中的消息既可,其实本质是利用了MQ的广播机制在分布式的系统中传播消息,目前常用的有Kafka和RabbitMQ。利用bus的机制可以做很多的事情,其中配置中心客户端刷新就是典型的应用场景之一,我们用一张图来描述bus在配置中心使用的机制。
![](14-SpringCloud Bus消息总线/98b49d1f110fb29d51f17708446d7054.jpg)
根据此图我们可以看出利用Spring Cloud Bus做配置更新的步骤:
- 提交代码触发post给客户端A发送bus/refresh
- 客户端A接收到请求从Server端更新配置并且发送给Spring Cloud Bus
- Spring Cloud bus接到消息并通知给其它客户端
- 其它客户端接收到通知,请求Server端获取最新配置
- 全部客户端均获取到最新的配置
RabbitMQ环境配置
安装Erlang
- 下载地址: https://www.erlang.org/downloads, 选择OTP 24.1 Windows 64-bit Binary File (erlang.org) , win64 OTP 24.1.2 64-bit Windows installer (github.com)
- 运行otp_win64_24.1.exe, 安装Erlang
- 设置环境变量ERLANG_HOME:
- 此电脑 -> 右键属性 -> 高级系统设置 -> 高级 -> 环境变量 -> 系统变量 -> 新建ERLANG_HOME,
- 变量名:
ERLANG_HOME
, 变量值: 安装目录`` - 修改环境变量Path: 末尾追加:
;%ERLANG_HOME%\bin
(注: 分号视情况而定, 若原Path末尾有分号则不加, 无则加), 保存 - 打开命令行, 输入erl, 提示版本信息: Eshell V12.1.2 (abort with ^G), 说明Erlang安装 成功
安装RabbitMQ
- 下载地址: https://www.rabbitmq.com/install-windows.html, 选择rabbitmq-server-3.9.8.exe (github.com)
运行
rabbitmq-server-3.9.8.exe`, 安装RabbitMQ- 设置环境变量, 同上,
- 变量名:
RABBITMQ_SERVER
, 变量值:rabbitmq安装目录
- 修改
Path
: 追加:;%RABBITMQ_SERVER%\sbin
- 变量名:
- 命令行输入:
rabbitmqctl status
, 打印pid, 说明RabbitMQ
已安装成功, 且已启动- 但此时访问 http://localhost:15672/ 发现无法访问, 因为还未激活管理插件
激活可视化插件
进入到RabbitMQ安装目录, 运行命令:
rabbitmq-plugins.bat enable rabbitmq_management
, 出现下图说明插件安装成功![](14-SpringCloud Bus消息总线/6ac989be5e6609cc3e77687986a758da.png)
验证: 浏览器访问: http://localhost:15672/ 用户名 密码均为:
guest
, 至此, windows安装RabbitMQ已全部完成
CentOS安装
安装erlang
下载(会比较慢,请耐心等待)
1
2wget https://hub.fastgit.org/erlang/otp/releases/download/OTP-23.3.4.8/otp_src_23.3.4.8.tar.gz
yum -y install gcc glibc-devel make ncurses-devel openssl-devel xmlto perl wget gtk2-devel binutils-devel解压
1
tar -zxvf otp_src_23.3.4.8.tar.gz
移走
1
mv otp_src_23.3.4.8 /usr/local
切换目录
1
cd /usr/local/otp_src_23.3.4.8/
创建即将安装的目录
1
mkdir ../erlang
配置安装路径
1
./configure --prefix=/usr/local/erlang
安装
1 | make install |
查看一下是否安装成功
1
ll /usr/local/erlang/bin
添加环境变量
1
echo 'export PATH=$PATH:/usr/local/erlang/bin' >> /etc/profile
刷新环境变量
1 | source /etc/profile |
甩一条命令,瞬间进入了一个未知的世界
1
2erl
在里面输入halt().命令退出来(那个点号别忘记)
安装RabbitMQ
下载RabbitMQ
1
wget https://hub.fastgit.org/rabbitmq/rabbitmq-server/releases/download/v3.9.8/rabbitmq-server-generic-unix-3.9.8.tar.xz
由于是tar.xz格式的所以需要用到xz,没有的话就先安装
1
yum install -y xz
第一次解压
1
xz -d rabbitmq-server-generic-unix-3.9.8.tar.xz
第二次解压移走
1
tar -xvf rabbitmq-server-generic-unix-3.9.8.tar -C /usr/local/
改名
1
mv /usr/local/rabbitmq_server-3.9.8/ /usr/local/rabbitmq
配置环境变量
1
echo 'export PATH=$PATH:/usr/local/rabbitmq/sbin' >> /etc/profile
刷新环境变量
1
source /etc/profile
启动:
1
rabbitmq-server -detached
停止:
1
rabbitmqctl stop
状态:
1
rabbitmqctl status
防火墙之类的请自行处理(5672和15672端口),反正我是从来不开防火墙。
开启web插件,访问:http://127.0.0.1:15672/
1
rabbitmq-plugins enable rabbitmq_management
用户管理
查看所有用户
1
rabbitmqctl list_users
添加一个用户
1
rabbitmqctl add_user sun sun
配置权限
1
rabbitmqctl set_permissions -p "/" sun ".*" ".*" ".*"
查看用户权限
1
rabbitmqctl list_user_permissions sun
设置tag
1
rabbitmqctl set_user_tags sun administrator
删除用户(安全起见,删除默认用户)
1
rabbitmqctl delete_user guest
动态刷新(全局)
- 前提:必须先具备良好的RabbitMQ环境
- 以3355为模板制作一个3366
设计思想
利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
![](14-SpringCloud Bus消息总线/9a508988307961d28d911e64bc57abed.png)
利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
![](14-SpringCloud Bus消息总线/7b8f3734bf674c94bf10cb0df5c8b074.png)
第二种架构显然更加合适,第一种一不适合的原因如下
- 打破了微服务的职责单一性,因为微服务本身是业务模块,它不应该承担配置刷新的职责
- 打破了微服务各个节点的对等性
- 有一定的局限性。例如:微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就回增加更多的的修改
刷新客户端
3355客户端引入spring-cloud-starter-bus-amqp包,增加对消息总线的支持
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>配置文件需要增加RebbitMq的相关配置
1
2
3
4
5
6spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
测试
依次启动eureka,config-serve,config-client。修改config-client启动配置,同时在3355和3356端口启动服务。
启动完成后,浏览器分别访问连接:http://127.0.0.1:3355/configInfo ,http://127.0.0.1:3356/configInfo, 可以发现页面显示的内容都是:version = 3,说明客户端都已经读取到了server端的内容。
现在我们更新git仓库上的配置文件,将版本变成4,先访问一下 http://127.0.0.1:3355/configInfo,可以看到页面依然显示版本为3。
我们对端口为8081的服务发送一个/actuator/bus-refresh的POST请求,在win10下使用下面命令来模拟webhook。
1
A:\Users\sun>curl -X POST http://localhost:3355/actuator/bus-refresh
注意: 在springboot2.x的版本中刷新路径为:/actuator/bus-refresh,在springboot1.5.x的版本中刷新路径为:/bus/refresh。
刷新客户端(⭐)
Spring Cloud Bus做配置更新步骤如下:
- 提交代码触发post给Server端发送bus/refresh
- Server端接收到请求并发送给Spring Cloud Bus
- Spring Cloud bus接到消息并通知给其它客户端
- 其它客户端接收到通知,请求Server端获取最新配置
- 全部客户端均获取到最新的配置
给3344配置中心服务端添加消息总线支持,引入spring-cloud-starter-bus-amqp包,增加对消息总线的支持
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>配置文件需要增加RebbitMq的相关配置,actuator开启所有访问。
1
2
3
4
5
6
7
8
9
10
11spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: "*" # "bus-refresh"
测试
依次启动eureka,config-serve,config-client。修改config-client启动配置,同时在3355和3356端口启动服务。
按照上面的测试方式,访问两个客户端测试均可以正确返回信息。同样修改配置文件,将版本改为5并提交到仓库中。在win10下使用下面命令来模拟webhook。
1
curl -X POST http://localhost:3344/actuator/bus-refresh
执行完成后,依次访问两个客户端,返回版本为5的信息。说明三个客户端均已经拿到了最新配置文件的信息。
动态刷新(定点)
不想全部通知,只想指定具体某一实例生效而不是全部
公式:
http://ip:配置中心的端口/actuator/bus-refresh/{destination}
/bus-refresh
请求不再发送到具体的服务实例上,而是发给config server 。并通过destination参数类指定需要发送更新配置的服务或者实例- destination值为
{spring.application.name}:{server.port}
我们只想刷新运行在3355端口上的config-client为例, 只通知3356,不通知3355
1
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3356"
![](14-SpringCloud Bus消息总线/17010aff8d3b472d2ba448bfce6a7067.png)
Config与Eureka配合
默认情况下,配置客户端启动时,都是通过配置属性
spring.cloud.config.uri
绑定到配置服务器,并使用远程属性初始化 Spring Environment。这样做的最终结果是所有想要使用Config Server的客户端必须在bootstrap.yml中配置spring.cloud.config.uri
(如上面的”http://localhost:3344"),这种方式无法利用服务发现组件的优势。如果您正在使用DiscoveryClient实现,可将ConfigServer与Eureka等注册中心联合使用(目前Spring Cloud只支持与Eureka及Consul联合使用,不支持与Zookeeper联合使用)。
但是如果配置了 spring.cloud.config.uri ,客户端将无法利用注册。使用服务发现的坏处是启动时额外的网络往返,以定位服务注册。好处是配置服务器可以更改其坐标,只要发现服务是一个固定点(如项目名称不变)。
将服务中心3344 注册到eureka
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
32server:
port: 3344
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
# git仓库地址
uri: git@gitee.com:fulsun/scconfig.git
# username:
# password:
# 搜索目录
search-paths:
- springcloud-config
label: master #读取分支
rabbitmq:
addresses: 192.168.61.45
port: 5672
username: sun
password: sun
management: # 暴露触发消息总线的地址
endpoints:
web:
exposure:
include: "bus-refresh"修改客户端 eureka-client 的配置文件bootstrap.yml内容,并移除application.yml中eureka的配置, 放到bootstrap.yml中
- 因为bootstrap.yml在application.yml之前加载,若将eureka配置放在application.yml里,启动时没有获取到defaultZone就会从自动配置的服务注册中心抓取服务信息,导致获取eureka信息失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14spring:
cloud:
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述三个综合http://localhost:3344/master/config-dev.yml
- uri: http://localhost:3344 #配置中心的地址
+ discovery:
+ enabled: true
+ service-id: cloud-config-center
+eureka:
+ client:
+ service-url:
+ defaultZone: http://127.0.0.1:7001/eureka/想要将Config Server与注册中心联合使用,只需要在客户端配置
spring.cloud.config.discovery.enabled:true
和spring.cloud.config.discovery.serviceId
两个配置项即可(serviceId是注册到Eureka中的Application.name)。重启微服务3356,可以看到根据服务注册中心找到了配置中西3344的地址。
![](14-SpringCloud Bus消息总线/0d64ae5d8d71c7b4e411c8198cac6218.png)
访问 http://127.0.0.1:3356/configInfo 后,能成功获取外部的配置文件信息,表示无问题。
总结
bus总线通知的步骤
![](14-SpringCloud Bus消息总线/f180781dc8cf02b97265568d9247cb2e.png)