spring cloud bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其他的消息指令。spring bus的一个核心思想是通过分布式的启动器对spring boot应用进行扩展,也可以用来建立一个多个应用之间的通信频道。目前唯一实现的方式是用amqp消息代理作为通道,同样特性的设置(有些取决于通道的设置)在更多通道的文档中。
spring cloud bus被国内很多都翻译为消息总线,也挺形象的。大家可以将它理解为管理和传播所有分布式项目中的消息既可,其实本质是利用了mq的广播机制在分布式的系统中传播消息,目前常用的有kafka和rabbitmq。利用bus的机制可以做很多的事情,其中配置中心客户端刷新就是典型的应用场景之一,我们用一张图来描述bus在配置中心使用的机制。

根据此图我们可以看出利用spring cloud bus做配置更新的步骤:
1、提交代码触发post给客户端a发送bus/refresh
2、客户端a接收到请求从server端更新配置并且发送给spring cloud bus
3、spring cloud bus接到消息并通知给其它客户端
4、其它客户端接收到通知,请求server端获取最新配置
5、全部客户端均获取到最新的配置
客户端spring-cloud-config-client改造
需要多引入<code>spring-cloud-starter-bus-amqp</code>包,增加对消息总线的支持
配置文件需要增加rebbitmq的相关配置,这样客户端代码就改造完成了。
依次启动spring-cloud-eureka、spring-cloud-config-server、spring-cloud-config-client项目,在启动spring-cloud-config-client项目的时候我们会发现启动日志会输出这样的一条记录。
说明客户端已经具备了消息总线通知的能力了,为了更好的模拟消息总线的效果,我们更改客户端spring-cloud-config-client项目的端口为8003、8004依次启动,这样测试环境就准备好了。启动后eureka后台效果图如下:
我们先分别测试一下服务端和客户端是否正确运行,访问:<code>http://localhost:8001/neo-config/dev</code>,返回信息:
说明server端都正常读取到了配置信息。
依次访问:<code>http://localhost:8002/hello</code>、<code>http://localhost:8003/hello</code>、<code>http://localhost:8004/hello</code>,返回:<code>hello im dev</code>。说明客户端都已经读取到了server端的内容。
现在我们更新<code>neo-config-dev.properties</code> 中<code>neo.hello</code>的值为<code>hello im dev update</code>并提交到代码库中,访问:<code>http://localhost:8002/hello</code> 依然返回<code>hello im dev</code>。我们对端口为8002的客户端发送一个<code>/bus/refresh</code>的post请求。在win下使用下面命令来模拟webhook.
执行完成后,依次访问:<code>http://localhost:8002/hello</code>、<code>http://localhost:8003/hello</code>、<code>http://localhost:8004/hello</code>,返回:<code>hello im dev update</code>。说明三个客户端均已经拿到了最新配置文件的信息,这样我们就实现了图一中的示例。
在上面的流程中,我们已经到达了利用消息总线触发一个客户端<code>bus/refresh</code>,而刷新所有客户端的配置的目的。但这种方式并不优雅。原因如下:
打破了微服务的职责单一性。微服务本身是业务模块,它本不应该承担配置刷新的职责。
破坏了微服务各节点的对等性。
有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就不得不修改webhook的配置。
因此我们将上面的架构模式稍微改变一下
这时spring cloud bus做配置更新步骤如下:
1、提交代码触发post请求给bus/refresh
2、server端接收到请求并发送给spring cloud bus
这样的话我们在server端的代码做一些改动,来支持<code>bus/refresh</code>
配置文件增加rebbitmq的相关配置,关闭安全验证。这样server端代码就改造完成了。
依次启动spring-cloud-eureka、spring-cloud-config-server、spring-cloud-config-client项目,改动spring-cloud-config-client项目端口为8003、8004依次启动。测试环境准备完成。
按照上面的测试方式,访问server端和三个客户端测试均可以正确返回信息。同样修改<code>neo-config-dev.properties</code> 中<code>neo.hello</code>的值为<code>hello im dev update</code>并提交到代码库中。在win下使用下面命令来模拟webhook触发server端<code>bus/refresh</code>.
执行完成后,依次访问:<code>http://localhost:8002/hello</code>、<code>http://localhost:8003/hello</code>、<code>http://localhost:8004/hello</code>,返回:<code>hello im dev update</code>。说明三个客户端均已经拿到了最新配置文件的信息,这样我们就实现了上图中的示例。
某些场景下(例如灰度发布),我们可能只想刷新部分微服务的配置,此时可通过<code>/bus/refresh</code>端点的destination参数来定位要刷新的应用程序。
例如:<code>/bus/refresh?destination=customers:8000</code>,这样消息总线上的微服务实例就会根据destination参数的值来判断是否需要要刷新。其中,<code>customers:8000</code>指的是各个微服务的applicationcontext id。
destination参数也可以用来定位特定的微服务。例如:<code>/bus/refresh?destination=customers:**</code>,这样就可以触发customers微服务所有实例的配置刷新。
一些场景下,我们可能希望知道spring cloud bus事件传播的细节。此时,我们可以跟踪总线事件(remoteapplicationevent的子类都是总线事件)。
跟踪总线事件非常简单,只需设置<code>spring.cloud.bus.trace.enabled=true</code>,这样在<code>/bus/refresh</code>端点被请求后,访问<code>/trace</code>端点就可获得类似如下的结果:
这个日志显示了<code>customers:8001</code>发出了refreshremoteapplicationevent事件,广播给所有的服务,被<code>customers:9000</code>和<code>stores:8081</code>接受到了。想要对接受到的消息自定义自己的处理方式的话,可以添加<code>@eventlistener</code>注解的ackremoteapplicationevent和sentapplicationevent类型到你自己的应用中。或者到tracerepository类中,直接处理数据。
这样,我们就可清晰地知道事件的传播细节。
<code>/bus/refresh</code> 有一个很严重的bug,一直没有解决:对客户端执行<code>/bus/refresh</code>之后,挂到总线的上的客户端都会从eureka注册中心撤销登记;如果对server端执行<code>/bus/refresh</code>,server端也会从eureka注册中心撤销登记。再用白话解释一下,就是本来人家在eureka注册中心注册的好好的,只要你对着它执行一次<code>/bus/refresh</code>,立刻就会从euraka中挂掉。
其实这个问题挺严重的,本来你利用<code>/bus/refresh</code>给所有的节点来更新配置信息呢,结果把服务从euraka中给搞掉了,那么如果别人需要调用客户端的服务的时候就直接歇菜了。不知道国内有童鞋公司在生产中用到这个功能没有,用了不就很惨烈。在网上搜索了一下,国内网友和国外网友都遇到过很多次,但是一直没有解决,很幸运就是我在写这篇文章的前三天,netflix修复了这个问题,使用spring cloud最新版本的包就可以解决这个问题。由此也可以发现spring cloud还在快速的发展中,最新的版本可能也会有一些不稳定性,可见路漫漫而修远兮。
在pom中使用spring cloud的版本,解决这个bug.
主要是这句:<code><spring-cloud.version>dalston.sr1</spring-cloud.version></code> ,详情可以参考本文示例中的代码
bug的讨论和解决过程可以看github上面这两个issue:
<a href="https://github.com/spring-cloud/spring-cloud-config/issues/692">/bus/refresh causes instances registered in eureka server disappeared #692</a>
<a href="https://github.com/spring-cloud/spring-cloud-netflix/issues/1857">making post on ‘refresh’ permamently deregisters the service from eureka #1857</a>
参考:
<a href="http://www.itmuch.com/spring-cloud/spring-cloud-bus-auto-refresh-configuration/">config server——使用spring cloud bus自动刷新配置</a>
<a href="http://blog.didispace.com/springcloud7/">spring cloud构建微服务架构(七)消息总线</a>
<a href="https://github.com/ityouknow/spring-cloud-starter">示例代码</a>
作者:纯洁的微笑
版权归作者所有,转载请注明出处