目录

spirngcloud消息总线bus

针对 Spring Cloud Config 存在的问题:

  1. 无法真正实现:一处通知、处处生效;
  2. 无法实现精确通知,只通知集群中的某些服务(精确通知,比如有100台机器,只通知前98台)

带着上面这两个问题,我们来聊聊 Spring Cloud Bus 服务总线。

演示程序方案

  1. 两个客户端工程,3355,3366(参照3355新建)
  2. 配置中心3344,连github
  3. github上修改配置,刷新3344,3355和3366同时更新并能访问最新值

概述

Spring Cloud Bus 文档

本篇主要介绍spring cloud bus配合spring cloud config使用实现配置的动态刷新

Bus是什么

在微服务架构的系统中,通常会使用 轻量级的消息系统 来构建一个共用的消息主题,并让系统中所有的微服务示例都连接上来。由于 该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便的广播一些需要让其他连接在该主题上的实例都知道的消息。

Spring Cloud Bus 是用来将 分布式系统的节点轻量级消息系统连接起来的框架,它整合了 Java 的事件处理机制和消息中间件的功能。Spring Cloud Bus 目前仅支持 RabbitMQ 和 Kafka。

Bus原理

Config 客户端示例,都去监听 MQ 中的同一个 topic(默认是 springCloudBus)。当一个服务刷新数据的时候,它会把这个消息放入到 Topic 中,这样其他监听同一 Topic 的服务就能够得到通知,然后去更新自身的配置。就是通过 MQ 消息队列的 Topic 机制,达到广播的效果。

20201112200449

两种刷新方式

选用 Spring Cloud Bus 进行 Topic 消息的发送,在技术选型上共有两种设计思想:

触发客户端

利用消息总线,触发一个客户端/bus/refresh 端点。通过客户端向 Bus 总线发送消息,实现刷新所有客户端的配置。

http://img.cana.space/picStore/20201112200757.png

触发服务端

利用消息总线,触发一个服务端/bus/refresh 端点。通过Config Server 服务端向 Bus 总线发送消息,实现刷新所有客户端的配置。

http://img.cana.space/picStore/20201112200813.png

如何选择

根据架构图显然 图二 更加合适,图一触发客户端方式 不适合的原因如下:

  1. 利用消息总线触发客户端方式,打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责;
  2. 触发客户端方式,破坏了微服务各个节点之间的对等性(比如说:3355/3366/3377 集群方式提供服务,此时 3355 还需要消息通知,影响节点的对等性)
  3. 有一定的局限性。当微服务迁移时,网络地址会经常发生变化,如果此时需要做到自动刷新,则会增加更多的修改。

spring cloud bus动态刷新全局广播

  1. 启动rabbitmq服务器,客户端port 5672,管理界面端口15672

  2. 给cloud-config-center3344配置中心服务端添加消息总线支持

    pom

    添加消息总线rabbitmq的支持

    1
    2
    3
    4
    5
    
    <!--添加消息总线rabbitmq支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    

    yml

    增加rabbitmq环境配置以及暴露bus刷新配置的端点

     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
    
    server:
      port: 3344
       
    spring:
      application:
        name: cloud-config-center
      cloud:
        config:
          server:
            git:
              uri: git@github.com:lienhui68/microservice-cloud-config.git
              # 搜索目录
              search-paths: microservice-cloud-config
              # 注意github默认主分支是main,但是config默认分支是master,所以需要修改,master->main
              default-label: main
          ####读取分支
          label: main
      #rabbitmq相关配置
      rabbitmq:
        host: localhost
        port: 5672
        username: admin
        password: admin
    #rabbitmq相关配置,暴露bus刷新配置的端点
    management:
      endpoints:
        web:
          exposure:
            #暴露bus刷新配置的端点
            include: "bus-refresh"
    
  3. 客户端cloud-config-client3355 配置

    pom

    同样添加消息总线rabbitmq的支持

    1
    2
    3
    4
    5
    
    <!--添加消息总线rabbitmq支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    

    yml

    只需要添加rabbitmq环境配置,之前暴露监控端点的配置可以删除,包括删除@RefreshScope

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    server:
      port: 3355
    spring:
      application:
        name: cloud-config-client
      cloud:
        # 下面四项拼起来就是http://localhost:3344/main/config-dev.yml
        config:
          # 分支名称
          label: main
          # 配置文件名称
          name: config
          # 环境profile
          profile: dev
          # 配置中心地址
          uri: http://localhost:3344
      #rabbitmq相关配置
      rabbitmq:
        host: localhost
        port: 5672
        username: admin
        password: admin
    
  4. copy cloud-config-client3355,新增cloud-config-client3366客户端工程,修改端口号为3366

  5. 启动配置中心3344,启动3355和3366

  6. 模仿运维修改github配置,刷新配置中心

    1
    
    $ curl -X POST "http://localhost:3344/actuator/bus-refresh"
    
  7. 访问3344、3355、3366,发现配置都更新了

    • http://localhost:3344/config-dev.yml

    • http://localhost:3355/info

    • http://localhost:3366/info

      http://img.cana.space/picStore/20201112221037.png

  8. 观察rabbitmq管理界面,发现多了个topic类型的exchange,并且绑定了三个queue,这3个queue指向的客户端就是上面这个应用

    http://img.cana.space/picStore/20201112221315.png

spring cloud bus动态刷新定点通知

如果需要 差异化通知,并不想进行全局广播,此时就用到了 Bus 的 定点通知 功能。

此次我们通过客户端集群(3344/3355)演示。GitHub 远程配置修改后 ,进行差异化定点通知,只通知 3355,不通知 3366。此处命令和全局广播有点不同,命令为:http://配置中心IP:配置中心的端口号/actuator/bus-refresh/{destination}

通过指定 /bus/refresh请求 不再发送到具体的服务实例上,而是发给 Config Server 并通过 {destination} 参数 来指定需要更新配置的服务或实例。

{destination} 参数 = 微服务名 :端口号。3355 微服务名为:cloud-config-client。此处最终发送的 Post 请求命令为:$ curl -X POST "http://localhost:3344/actuator/bus-refresh/cloud-config-client:3355",达到 精确通知 效果。

执行之后,3355能够识别更新,3366依然保持原值

使用WebHooks实现自动刷新

官方文档说明

到目前为止已经很好了,但是运维小伙伴还需要执行一次curl,不太完美,可以使用webhooks来触发后面的一系列动作,流程如下:

http://img.cana.space/picStore/20201112223313.png

GIT上的webhook更新调用config server的/monitor(spring-cloud-config-monitor)触发RefreshRemoteApplicationEvent事件,然后spring cloud bus的StreamListener监听RemoteApplicationEvent,通过mq发布到每个config client,然后client接收RemoteApplicationEvent事件来实现refresh。

注意

注:这里简单说一下spring cloud bus 和spring cloud stream ,2者都是用来消息代理的,bus主要利用广播机制实现自动刷新配置,stream主要用于服务间的消息调用。

其实,bus也是基于stream的,它使用了一部分stream的功能。

spring cloud bus实现自动刷新配置的方式有2种:

  1. 第一种,bus 的refresh操作发生在client端,然后client端通知消息总线bus这个更新信息,bus再通知其他client端更新

  2. 第二种,bus的refresh操作发生在config server端,config server通知bus,再通知其他client端去更新

这里我们使用第二种方式,原理图如下:

20201112223453

这里的远端GIT使用的是Github(码云),其 WebHooks 配置方法如下

  1. 本地测试的话,还需要一个内网穿透,可以使用ngork或者natapp之类的东东,这里使用ngrok

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    ngrok http 3344
    ngrok by @inconshreveable                                                                                                                                                 (Ctrl+C to quit)
       
    Session Status                online
    Account                       david (Plan: Free)
    Version                       2.3.35
    Region                        United States (us)
    Web Interface                 http://127.0.0.1:4040
    Forwarding                    http://0301806f1311.ngrok.io -> http://localhost:3344
    Forwarding                    https://0301806f1311.ngrok.io -> http://localhost:3344
       
    Connections                   ttl     opn     rt1     rt5     p50     p90
                                  0       0       0.00    0.00    0.00    0.00
    

    现在可以在github上配上域名0301806f1311.ngrok.io了,先本地测试下

    http://img.cana.space/picStore/20201112232417.png

  2. github上设置webhooks

    http://img.cana.space/picStore/20201112234834.png

  3. 配置中心config server端加入monitor

    1
    2
    3
    4
    
          <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-monitor</artifactId>
            </dependency>
    
  4. 客户端加入bus.id,至于为什么加,后面会有解释

    1
    2
    
    bus:
      id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}
    

    完整配置:

     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
    32
    33
    
    server:
      port: 3355
    spring:
      application:
        name: cloud-config-client
      cloud:
        # 下面四项拼起来就是http://localhost:3344/main/config-dev.yml
        config:
          # 分支名称
          label: main
          # 配置文件名称
          name: config
          # 环境profile
          profile: dev
          # 配置中心地址
          uri: http://localhost:3344
        bus:
          id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}
       
      #rabbitmq相关配置
      rabbitmq:
        host: localhost
        port: 5672
        username: admin
        password: admin
        virtual-host: eh-vhost
       
    # 暴露监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
  5. 测试访问

    http://img.cana.space/picStore/20201113000812.png

使用webhooks无法刷新client配置的解决方案

发现问题

使用config手动通过访问/actuator/bus-refresh可以正常刷新,但是通过配置webhooks访问/monitor无法刷新配置。

解决问题

官方文档排查

https://cloud.spring.io/spring-cloud-bus/spring-cloud-bus.html bus的文档中对spring.cloud.bus.id有如下描述:

http://img.cana.space/picStore/20201113001051.png

应用有一个ServiceID,默认的值是app:index:id的组装。规则是:

  • app :如果vcap.application.name存在,使用vcap.application.name,否则使用spring.application.name(默认值为application)
  • index :配置值的情况下优先使用vcap.application.instance_index,否则依次使用spring.application.index、local.server.port、server.port(默认值0)
  • id: 如果vcap.application.instance_id存在,使用vcap.application.instance_id,否则给一个随机值

代码排查

设置客户端的打印日志级别

1
2
3
logging:
  level:
    org.springframework.cloud.bus: debug

控制台会打印出org.springframework.cloud.bus.DefaultBusPathMatcher中匹配规则的日志(如果客户端不刷新,一般这里的日志打印出的匹配规则和待匹配字符串是不一致的),webhooks端的过来匹配规则由三部分数据组成,使用“:”拼接,得到的结果如下:

1
spring.application.name:spring.cloud.config.profile:**

如果我们serviceID不进行设置,当前服务那么会使用默认配置(默认配置代码体现在:org.springframework.cloud.bus.BusEnvironmentPostProcessor#getDefaultServiceId),如下:

1
2
3
private String getDefaultServiceId(ConfigurableEnvironment environment) {
		return "${vcap.application.name:${spring.application.name:application}}:${vcap.application.instance_index:${spring.application.index:${local.server.port:${server.port:0}}}}:${vcap.application.instance_id:${random.value}}";
	}

对应官方文档,以及我们的配置文件,我们可以依据serviceID的匹配规则来设置对应的参数去匹配webhooks。

如果发现app:index:id中的index不一致, 举例yml配置:

1
2
3
vcap:
  application:
    instance_index: ${spring.cloud.config.profile}

或者直接修改bus.id的配置,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
spring:
  application:
    name: client
  cloud:
    config:
      discovery:
        service-id: CONFIG
        enabled: true
      profile: dev 
    bus:
      id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}