消息总线是一种通信工具,可以在机器之间互相传输消息、文件等,他扮演着一种消息路由的角色,拥有一套完备的路由机制来决定消息传输方向。发送端只需要向消息总线发出消息,而不用管消息被如何转发。
Spring Cloud Bus通过轻量消息代理连接各个分布的节点。管理和传输所有分布式项目中的消息,本质是利用了MQ的广播机制在分布式的系统中传输消息,目前常用的有Kafka和RabbitMQ等。
在消息总线中,常见的设计模式有点对点模式及订阅/发布模式。
点对点模式包含三个角色:消息队列(Queue)、生产者(Producer)、消费者(Consumer)。点对点模式中的每个消息都被发送到一个特定的队列,消费中从队列中获取消息。队列中保留着消息,直到他们被消费或超时。点对点模式的运行流程图如下图:
点对点模式具有以下特点:
订阅/发布模式包含三个角色:主题(topic)、发布者(Publisher)、订阅者(Subscriber)。
订阅/发布模式中,多个发布者将消息发送到对应的主题,系统将这些消息传递给多个订阅者。下图展示了订阅/发布模式的运行流程图:
订阅/发布模式具有以下特点:
在微服务架构中,经常会使用REST服务作为服务间的通信机制。REST以其轻量、简单、易理解而著称,但这种通信机制也并非适合所有的场景。例如,在一些高并发、高可靠、实时的场景,则需要消息总线来帮忙。消息总线具有以下几个特点:
与REST服务的“请求-相应”模式不同,消息总线的实时性非常高。使用了消息总线,生产者一方只要把消息往队列里一扔,就可以立马返回,相应用户了。无需等待处理结果,实现了异步处理。
同时,对于消费者而言,消费者对于消息的到达感知也非常及时。消费者会对消息总线进行监听,只要有消息进入队列,就可以马上得到通知。这种优势是REST服务所不能具备的。在REST服务中,要想及时获取到更新消息,就不得不进行轮询,这往往非常低效。
在消息总线中,生产者负责将消息发送到队列中,而消费者把消息从队列中取出来。生产者无需等待消费者启动,消费者也无需关心生产者是否已经处于就绪状态。所以,这种模式能很好的实现生产者与消费者的解耦。
然而,如果是在REST服务中,服务调用方必须等待服务的提供方准备好了才能调用,否则就会调用失效。
消息总线拥有对其他通信方式更高的成功率。一方面,生产者与消费者之间实现了解耦,所以,生产者和消费者之间不存在强关联关系,即便是生产者或消费者任意一方掉线了,也不会影响消息最终的送达;另一方面,消息总线往往会结合数据库来实现消息的持久化,并设置状态标识。只有消息消费成功,才会去修改状态标志。
消息总线同时还承担着缓存区的作用。大量业务消息首先会进入消息队列进行缓存,消息的消费者可以根据自己的处理能力来进行消费,所以不管消息的数据量有多少,都不会对消费者造成冲击。
Spring Cloud Bus通过轻量消息代理连接各个分布的节点,管理和传播所有分布式项目中的消息,本质是利用了消息中间件的广播机制在分布式的系统中传播消息。
目前,Spring Cloud Bus所支持的常用的消息中间件有RabbitMQ和Kafka,使用时只需要添加spring-cloud-starter-bus-amqp或spring-cloud-starter-bus-kafka依赖即可。同时,需要确保相关的消息中间件连接配置正确。
Spring Cloud Bus支持消息发送到所有已监听的节点,或者某个特定服务的所有节点。同时,Spring Cloud Bus提供了一些HTTP接口/bus/*,用于触发Spring Cloud Bus内部的事件。
目前,Spring Cloud Bus主要有以下两个接口实现:
所以,Spring Cloud Bus结合Spring Cloud Config的使用,可以实现配置文件的自动更新。接下来的例子我们来实现这个功能。
pom文件添加依赖
org.springframework.cloud spring-cloud-starter-bus-amqp
修改application.properties
spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest #值设为false,用于禁用安全管理设置,利于本地调试。如果实在公司内网部署,不仅有物理隔离,也可以禁用安全管理设置 management.security.enabled=false
下载安装运行RabbitMQ
动态加载变量的类上面加载@RefreshScope注解
@RestController @RefreshScope public class HelloController { @Value("${hello}") private String hello; @RequestMapping("/hello") public String hello() { return hello; } }
Spring Cloud Bus提供了多种方式来更新微服务实例的配置信息。总结如下:
使用/refresh方法,可以更新单个微服务实例配置。例如,微服务实例 msa-weather-city-service 部署在8082端口,则发送POST请求到http://localhost:8082/refresh,可以出发该微服务实例,去获取最新的配置信息。
同样的,发送POST请求到http://localhost:8082/bus/refresh,可以出发该微服务实例,去获取最新的配置信息。同时,使用/bus/refresh方法,可以更新多个微服务实例的配置信息。例如,在8081和8083上都不熟了微服务实例,当使用/bus/refresh方法在任意一个微服务实例上触发时,另外一个微服务实例也能自动更新。这就是Spring Cloud Bus所带来的好处,让更多信息在多个微服务实例之间进行广播,从而能够通知到所有的微服务实例。
一般,当为服务的配置需要更新时,并不会在每个微服务实例上去触发更新信息,而是去触发配置服务器上的/bus/refresh方法,从而将更新事件发送给所有的微服务实例。
某些场景下(如灰度发布),可能只想刷新部分微服务的配置,此时,可通过/bus/refresh断点的destination参数来定位要刷新的微服务实例。
例如,/bus/refresh?destination=msa-weather-city-service:8082,这样消息总线上的微服务实例就会根据destination参数的值来判断是否需要刷新。
虽然使用触发/bus/refresh请求到配置服务器,可以避免手动刷新微服务实例配置的繁琐过程,但该触发过程仍然是手动的。是否可以自动来刷新配置呢?比如当配置的Git仓库中变更了,可否能够及时通知到配置服务器呢?当然是可以的,借助Git仓库的Webhook功能就能实现这个目的。
配置信息自动更新的流程为:
使用GitHub的Webhook,只需要在GitHub的Payload URL填写相应的配置中心触发刷新的地址即可。URL需是外网能访问到的。