Spring Cloud Bus 为 Spring 的事件、消息总线,通过轻量消息代理连接各个分布的节点,用于在集群(例如,配置变化事件)中传播状态变化,或者其他的消息指令,可与 Spring Cloud Config 联合实现热部署。
Spring Cloud Bus 的一个核心思想是通过分布式的启动器对 Spring Boot 应用进行扩展,也可以用来建立一个多个应用之间的通信频道。目前唯一实现的方式是用 AMQP 消息代理作为通道。
Spring Cloud Bus 被国内很多都翻译为消息总线,也挺形象的。大家可以将它理解为管理和传播所有分布式项目中的消息既可,其实本质是利用了 MQ 的广播机制在分布式的系统中传播消息,目前常用的有 Kafka 和 RabbitMQ。利用 Bus 的机制可以做很多的事情,其中配置中心客户端刷新就是典型的应用场景之一。
我们继续使用 上一节 写好的项目来做相应的改造。这里我们需要装 RabbitMQ,点击 rabbitmq 下载。也需要 curl 命令包。这些需要先在本机安装好。
org.springframework.cloud
spring-cloud-config-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-bus
org.springframework.cloud
spring-cloud-stream-binder-rabbit
org.springframework.boot
spring-boot-starter-test
test
eureka:
client:
service-url:
defaultZone: http://localhost:8090/eureka/ # 指定进行服务注册的地址。高可用配置中心-服务端服务化
server:
port: 8053
spring:
application:
name: wei-config-server
cloud:
config:
label: master # 对应 Git 上不同的分支,默认为 master
server:
git:
username:
password:
uri: https://github.com/itanping/wei-springcloud # 配置 Git 仓库地址
search-paths: wei-config/config-profile # Git仓库地址下的相对地址,可以配置多个,用,分割
bus:
enabled: true
trace:
enabled: false
management:
endpoints:
web:
exposure:
include: bus-refresh
启动类与上一节相同,无需改造。
package com.wei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* Config Server
* 注解@EnableConfigServer,开启对配置中心的支持
*/
@SpringBootApplication
@EnableConfigServer
public class WeiConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(WeiConfigServerApplication.class, args);
}
}
到此,Config Server 服务端改造完成。
启动。
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-bus
org.springframework.cloud
spring-cloud-stream-binder-rabbit
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-bus-amqp
org.springframework.boot
spring-boot-starter-test
test
还是分为两个配置文件,分别如下:
bootstrap.yml
eureka:
client:
service-url:
defaultZone: http://localhost:8090/eureka/ # 指定进行服务注册的地址。高可用配置中心实现
spring:
application:
name: wei-config-client
cloud:
config:
# uri: http://localhost:8053 # 配置服务中心的具体地址,即 config-server
name: config-client # 对应配置文件名 config-client-dev.properties 的 {application} 部分
profile: dev # 对应配置文件名 config-client-dev.properties 的 {profile} 部分
label: master # 使用 {label} 对应 Git 的分支名,如果配置中心使用的是本地存储,则该参数无用
discovery:
enabled: true # 开启 Config 服务发现支持
service-id: wei-config-server # 高可用配置中心实现需要将uri配置去掉,使用service-id直接指向Server端地址
bus:
trace:
enabled: true
enabled: true
application.yml
server:
port: 8063
spring:
rabbitmq:
addresses: localhost
port: 5672 # client端通信口
username: admin
password: admin
配置 RabbitMQ,默认端口:
如果设置的是 RabbitMQ 上新建用户,则需要确保该用户的 “Can access virtual hosts” 项不为 “No access”,即 “Current permissions” 不能为 “... no permissions ...”。否则,使用新建用户作为测试配置时,运行启动类时会报 java.net.SocketException: Socket Closed 异常。
package com.wei.controller.demo;
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;
/**
* 注解@RefreshScope必须加,否则客户端会收到服务端的更新消息,但是更新不了,因为不知道更新哪里的
*/
@RestController
@RefreshScope
public class DemoController {
@Value("${config.env:env parameter error!}")
private String configEnv;
@Value("${config.tip:tip parameter error!}")
private String configTip;
@GetMapping("/demo/info")
public String hello() {
String result = "[Config Client] env:" + configEnv + ", tip:" + configTip;
return result;
}
}
注解@RefreshScope必须加,否则客户端会受到服务端的更新消息,但是更新不了,因为不知道更新哪里的。
启动类与上一节相同,无需改造。
package com.wei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
@SpringBootApplication
public class WeiConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(WeiConfigClientApplication.class, args);
}
}
到此,Config Client 客户端改造完成。
启动。
确认已经分别依次启动以下模块:
启动完成后,可以看看 RabbitMQ 管理界面(http://localhost:15672/),RabbitMQ 会自动创建一个 topic 类型的 Exchange 和两个以 springCloudBus.anonymous. 开头的匿名 Queue。
访问:http://localhost:8063/demo/info,http://localhost:8064/demo/info,返回信息相同:
[Config Client] env:dev, tip:Demo for Spring Cloud config 2018-12-25
将 Git 中的配置 tip 值改为 config.tip=Demo for Spring Cloud config 2018-12-25 + bus
再访问:http://localhost:8063/demo/info,http://localhost:8064/demo/info,返回信息相同:
[Config Client] env:dev, tip:Demo for Spring Cloud config 2018-12-25
结果还是一样,也就是不管怎么刷新,都没有拿到变更后的内容。
这样,CMD 下执行命令,进行服务端刷新:
curl -X POST http://localhost:8053/actuator/bus-refresh/
或者使用 Postman 等工具,发送 http://localhost:8053/actuator/bus-refresh/ POST请求,返回 204 No Content 即OK。
然后再次访问:http://localhost:8063/demo/info,http://localhost:8064/demo/info
[Config Client] env:dev, tip:Demo for Spring Cloud config 2018-12-25 + bus
说明,对服务端使用 /actuator/bus-refresh 刷新后,可以获取到变更后的最新 Git 配置。
到这里,我们已经能够通过 Spring Cloud Bus 来实时更新总线上的属性配置了。
那如果对客户端使用 /actuator/bus-refresh 会发生什么呢?是只刷新了当前客户端还是刷新全部客户端,还是一个都没刷新呢?
不妨一试:
改造客户端 bootstrap.yml,添加以下配置,把客户端上的 bus-refresh 端点给放出来:
management:
endpoints:
web:
exposure:
include: bus-refresh
重启客户端,再访问:http://localhost:8063/demo/info 和 http://localhost:8064/demo/info,结果分别是原来的值:
[Config Client] env:dev, tip:Demo for Spring Cloud config 2018-12-25 + bus
将 Git 中的配置 tip 改为 config.tip=Demo for Spring Cloud config 2018-12-25 + BUS
同样的,CMD 下 执行以下命令,进行客户端刷新:
curl -X POST http://localhost:8063/actuator/bus-refresh/
从后台日志可以看到:
客户端刷新后,有了新的URL映射:
也会重新读取配置文件:
然后再次访问:http://localhost:8063/demo/info 和 http://localhost:8064/demo/info
[Config Client] env:dev, tip:Demo for Spring Cloud config 2018-12-25 + BUS
说明只要开启 Spring Cloud Bus 后,不管是对 Config Server 还是 Config Client 执行 /actuator/bus-refresh 都是可以更新配置的。
通过使用 Spring Cloud Bus 与 Spring Cloud Config 的整合,并以 RabbitMQ 作为消息代理,实现了应用配置的动态更新。
整个方案的架构如上图所示,其中包含了Git仓库、Config Server、以及微服务“Service A”的两个实例(Config Client),这两个实例中都引入了 Spring Cloud Bus,所以他们都连接到了 RabbitMQ 的消息总线上。
当我们将系统启动起来之后,“Service A”的两个实例会请求 Config Server 以获取配置信息,Config Server 根据应用配置的规则从Git仓库中获取配置信息并返回。
此时,若我们需要修改“Service A”的属性。首先,通过Git管理工具去仓库中修改对应的属性值,但是这个修改并不会触发“Service A”实例的属性更新。我们向“Service A”的其中一个实例(8063)发送POST请求,访问 /actuator/bus-refresh 接口。
此时,“Service A”的实例(8063)就会将刷新请求发送到消息总线中,该消息事件会被“Service A”的实例8063和实例8064从总线中获取到,并重新从 Config Server 中获取它们的配置信息,从而实现配置信息的动态更新。
这个架构,简而言之,就是服务的配置更新需要通过向具体服务中的某个实例发送请求,再触发对整个服务集群的配置更新。
这样的架构虽然能实现功能,但是结果是,我们指定的应用实例就会不同于集群中的其他应用实例,这样会增加集群内部的复杂度,不利于将来的运维工作。
我们需要优化这个架构,稍微调整一下:
调整点如下:
通过上面的优化,我们的服务实例就不需要再承担触发配置更新的职责。同时,对于 Git 的触发等配置都只需要针对 Config Server 即可,从而简化了集群上的一些维护工作。即我们这节所实现的内容。
是的,如果你也同步操作到了这里,你会发现你已经完成并优化了这个架构。
https://springcloud.cc/spring-cloud-bus.html
http://blog.didispace.com/springcloud7/
https://windmt.com/2018/04/19/spring-cloud-9-config-eureka-bus/
http://cloud.spring.io/spring-cloud-static/Finchley.RELEASE/single/spring-cloud.html#_spring_cloud_bus
SpringCloud进击 | 一浅出:服务注册与发现(Eureka)【Finchley版本】
SpringCloud进击 | 二浅出:服务消费者(Ribbon+REST)【Finchley版本】
SpringCloud进击 | 三浅出:服务消费者(Feign)【Finchley版本】
SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】
SpringCloud进击 | 五浅出:服务网关 - 路由(Zuul Router)【Finchley版本】
SpringCloud进击 | 六浅出:服务网关 - 过滤器(Zuul Filter)【Finchley版本】
SpringCloud进击 | 七浅出:配置中心(Git配置与更新)【Finchley版本】
SpringCloud进击 | 一深入:配置中心(服务化与高可用)【Finchley版本】
SpringCloud进击 | 二深入:配置中心(消息总线)【Finchley版本】
SpringCloud进击 | 三深入:服务链路跟踪(Spring Cloud Sleuth)【Finchley版本】
SpringCloud进击 | 四深入:服务链路跟踪(Sleuth+Zipkin+RabbitMQ整合)【Finchley版本】
SpringCloud进击 | 五深入:断路器监控(Hystrix Dashboard)【Finchley版本】
SpringCloud进击 | 六深入:断路器聚合监控(Hystrix Turbine)【Finchley版本】
SpringCloud进击 | 七深入:高可用的服务注册中心【Finchley版本】
下一节,请继续关注:SpringCloud进击 | 三深入:服务链路跟踪(Spring Cloud Sleuth)【Finchley版本】