(git上的源码:https://gitee.com/rain7564/spring_microservices_study/tree/master/seventh-spring-cloud-bus)
回顾
之前的Spring Cloud入门教程系列的一篇文章分布式配置——Spring Cloud Configuration的末尾讲到当托管在Spring Cloud Config Server的配置发生变化后, 可通过调用服务暴露的"/refresh" endpoint来更新某个服务实例的配置,http方法为POST,前提是服务引入特定依赖,且不关闭动态刷新功能,所需启动依赖和支持动态刷新的配置分别如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
endpoints:
refresh:
enabled: true
属性endpoints.refresh.enabled默认为true。所以若不需要关闭,可以不配置该属性。
但是,这种做法有一个很致命的弊端——一次只能对指定服务的某个实例进行刷新操作。而微服务架构下,一个服务通常会同时运行多个实例,所以当需要刷新配置时,就必须知道正在运行的所有实例的地址,然后逐个去刷新。这显然与Spring Cloud的自动化、尽量减少人工干预的理念相悖,而Spring Cloud的生态这么大,肯定有对应的解决之法,所以接下来将要讲解的Spring Cloud Bus就是为了解决这一难题而存在的。
虽然有web hook这样的技术配合"/refresh"实现动态刷新,但一个云应用可不止一个微服务存在,随着微服务数量增多,需要维护的刷新列表会越来越大,所以还是不推荐使用访问服务实例的/"refresh" endpoint 来刷新服务的配置。
何为Spring Cloud Bus
官方文档的介绍如下:
Spring Cloud Bus links nodes of a distributed system with a lightweight message broker. This can then be used to broadcast state changes (e.g. configuration changes) or other management instructions. A key idea is that the Bus is like a distributed Actuator for a Spring Boot application that is scaled out, but it can also be used as a communication channel between apps. Starters are provided for an AMQP broker as the transport or for Kafka, but the same basic feature set (and some more depending on the transport) is on the roadmap for other transports.
大致的意思是:Spring Cloud Bus(消息总线)通过一个轻量级的消息中间件可以连接分布式系统中的各个节点。可以使用该总线来广播某些状态的改变(比如配置信息发生变更)或其他管理指令。可以说,消息总线是spring boot应用扩展“道路”上的推进器,而且也把它用来作应用间相互通信的消息管道。(剩下的没什么营养就略过了,哈哈)
说实在的,我经常会觉得官网的文档有时很难去理解,但有时却又不得不去看官方文档不知各位看官是不是有同感,唉。。。为了让各位看官更好理解,这里就多费些口舌了。
在上一节消息驱动——Spring Cloud Stream中,我们可以体会到异步调用能让各个服务充分解耦而且也更加灵活。而Spring Cloud Bus就是借助消息驱动来实现将消息(事件)广播到各个服务中,然后服务对这些消息进行消费。我们都知道,多个服务可以监听同一个主题,多个服务也可以将消息发送到同一个主题,所以只要构建一个共用的topic(消息主题),然后各个服务实例都连接上来,当主题中有新的消息,那么会被所有监听该主题的服务实例消费,基于这样的特点,我们称它为消息总线。
各个实例可以通过该总线广播一些需要让其他服务实例都知道的消息,或者通知指定服务实例甚至指定服务的指定实例。总之,消息总线在微服务架构中被广泛使用,就像配置中心一样。
快速入门
改造代码
这里我们使用之前介绍Spring Cloud Config时创建的项目来进行改造。上一节介绍消息驱动——Spring Cloud Stream时使用的是RabbitMQ来作为消息中间件,所以这里也同样使用RabbitMQ来做示范。
首先,在licenseservice服务的pom文件中加入如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
这里虽说使用RabbitMQ作为消息中间件,但可以不用加入类似
spring-cloud-starter-stream-rabbit
的启动依赖,因为spring-cloud-starter-bus-amqp
已经引用了spring-cloud-starter-stream-rabbit
依赖。
然后,为了示范的方便,加入了ExamplePropertyController
,如下:
@RestController
public class ExamplePropertyController {
@Autowired
private ServiceConfig serviceConfig;
@GetMapping("/example/property")
public String example() {
return serviceConfig.getExampleProperty();
}
}
最后,为了能使用控制台启动多个licenseservice服务实例,将托管在config-server的licenseservice服务配置文件中的server.prot
属性迁移到bootstrap.yml
中。说的还不如画的,上图:
到这里,代码基本改造完毕,接下来是各位看官最关心的——验证。
接下来为了方便测试,使用config-server的classpath作为配置文件存储库,读者可以自己使用git作为存储库来测试。
动态刷新
在测试之前,有一点需要注意的,license-service服务的ServiceConfig
有没有加上@RefreshScope
注解,或者注解被注释掉了。
集体刷新
依次启动discovery-eureka、config-server服务,再启动两个license-service服务实例,端口号分别为10000和10001,。至于如何启动多个服务实例,可以参考另一篇文章服务注册与发现——Netflix Eureka,其中有介绍,在偏后的位置,也可以直接全局搜索-jar
。
启动完毕后,分别访问localhost:10000/example/property和localhost:10001/example/property。可以看到返回结果都是I am the default.
。
然后,修改config-server存储库license-service服务的application.yml
文件中的example.property
属性,比如修改为I am the default. And I have changed.
,接着重启config-server服务,这个操作类似于git的push。接下来就是不一样的地方了。
使用postman访问license-service服务某个实例的端点/bus/refresh
,http方法为POST
。比如访问10000端口的实例:localhost:10000/bus/refresh。
当成功返回后,可以在打印台中看到类似如下日志:
image.png
如果控制台打印了Received remote refresh request. Keys refreshed [example.property]
,就代表配置信息已经刷新完成。切换到10001端口实例的日志打印,同样也有刷新完成的日志打印,类似如下:
最后,分别访问localhost:10000/example/property和localhost:10001/example/property。现在的返回结果都是I am the default. And I have changed.
。
快十二点了,明天还要上班,暂时就先到这里哈。各位看官见谅,见谅~~~
(未完待续)
网友评论