八. SpringCloud消息匯流排

1. 消息匯流排概述

1.1 分散式配置的動態刷新問題
  • Linux運維修改Github上的配置文件內容做調整
  • 刷新3344,發現ConfigServer配置中心立刻響應
  • 刷新3355,發現ConfigClient客戶端沒有任何響應
  • 3355沒有變化除非自己重啟或者重新載入
  • 難道每次運維修改配置文件,客戶端都需要重啟?
1.2 消息匯流排加強Config

SpringCloud Bus配合SpringCloud Config使用可以實現配置的動態刷新。

image-20210304121847960

用SpringCloud Config時,我們可以實現配置資訊手動的動態刷新,也就是遠端配置資訊發生改變後,需要告訴服務端配置資訊發生變化後,服務端才會更新配置資訊,而現在我們想要實現分散式自動刷新配置資訊功能,這就需要我們使用SpringCloud Bus消息匯流排配合SpringCloud Config實現配置資訊的動態刷新。

SpringCloud Bus是用來將分散式系統的節點與輕量級消息系統連接起來的框架,它整合了Java的事件處理機制和消息中間件的功能,SpringCloud Bus目前支援兩種消息代理:RabbitMQ和Kafka。

SpringCloud Bus能管理和傳播分散式系統間的消息,就像一個分散式執行器,可用於廣播狀態更改、事件推送等, 也可以當做微服務間的通訊通道。

image-20210304122204181
1.2 為什麼被稱為匯流排

在微服務架構的系統中,通常會 使用輕量級的消息代理 來構建一個 共用的消息主題,並讓系統中所有微服務實例都連接上來,由於 該主題中產生的消息會被所有實例監聽和消費,所以稱它為消息匯流排。在匯流排上的各個實例,都可以方便地廣播一些需要讓其他連接在該主題上的實例都知道的消息。

基本原理:SpringCloud Config客戶端的實例都監聽消息隊列中的同一個主題(topic)(默認是SpringCloud Bus),當一個伺服器刷新數據的時候,它會把這個資訊放入到主題中,這樣其他監聽了同一主題的服務就能得到通知,然後去更新自身的配置。

1.3 安裝RabbitMQ

直接使用docker-compose安裝吧!

訪問地址看是否安裝成功://localhost:15672,輸入帳號並登錄,默認guest/guest

image-20210304145905115

2. SpringCloud Bus動態刷新全局廣播

在學習SpringCloud Config的時候我們已經建立了服務cloud-config-client-3355作為Config的客戶端,這裡為了演示廣播效果,我們增加複雜度,再以3355為模板再製作另一個Config的客戶端3366,建立Module:cloud-config-client-3366,其結構和cloud-config-client-3355類似,修改下服務埠號即可。

修改以下3355/3366的controller,用於區分

@RestController
@RefreshScope
public class ConfigClientController {
    @Value("${server.port}")
    private String serverPort;

    @Value("${server.info}") 
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo() {
        return "serverPort:" + serverPort + "\t configInfo:" + configInfo;
    }
}
2.1 消息匯流排的兩種設計思想

方案一

利用消息匯流排 觸發一個客戶端/bus/refresh端點,而後刷新所有客戶端的配置:

image-20210304151240935

方案二

利用消息匯流排 觸發一個服務端ConfigServer的/bus/refresh端點,而後刷新所有客戶端的配置:

image-20210304151317976

兩種方案對比

明顯第二種架構更加合適,第一種架構不合適的原因主要有:

  • 打破了微服務的職責單一性,因為微服務本身是業務模組,它本不應該承擔配置刷新的職責

  • 破壞了微服務各節點的對等性

  • 有一定的局限性,比如在微服務遷移時,它的網路地址常常會發生變化,此時如果想要做到自動刷新,那就會增加更多的修改

所以雖然從技術上兩種方案都可以實現,但是無疑在技術選型上我們應該選擇第二種方案。

2.2 3344服務端添加消息匯流排支援

在其POM文件中添加使用RabbitMQ實現消息匯流排的依賴:

<!--添加消息匯流排RabbitMQ支援-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

然後在其yml配置文件中添加RabbitMQ的相關配置,暴露SpringCloud Bus刷新配置的端點

# RabbitMQ相關配置
spring:
  rabbitmq:
    host: mpolaris.top
    port: 5672
    username: admin
    password: 1234321
# 暴露匯流排刷新配置的端點  
management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'
2.3 3355/3366客戶端添加消息匯流排支援

在其POM文件中添加RabbitMQ實現匯流排的依賴(同ConfigServer的相關依賴):

<!--添加消息匯流排RabbitMQ支援-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

然後在其yml配置文件中添加RabbitMQ的相關配置:

# RabbitMQ相關配置
spring:
  rabbitmq:
    host: mpolaris.top
    port: 5672
    username: admin
    password: 1234321
2.4 測試

按順序啟動Eureka服務註冊中心7001,ConfigServer配置中心3344,ConfigClient配置中心客戶端3355/3366,此時遠端GitHub上的配置資訊如下:

server:
  info: my name is config server , this is master/config-dev.yml

分別訪問配置中心及其配置中心客戶端的服務中心,發現結果都是一樣的。

然後將Gitee遠端的配置資訊中info加一個 =>,然後對服務配置中心發送POST請求:

curl -X POST "{配置中心的地址}/actuator/bus-refresh"

# 如此時我們該發送的POST請求為:
curl -X POST "//localhost:3344/actuator/bus-refresh"

此時再訪問配置中心及客戶端的配置資訊,發現所有服務的配置資訊都得到了更新:

image-20210304180104875

打開RabbitMQ的控制面板,我們可以發現有一個交換機,這就對應消息匯流排發送消息的交換機:

image-20210304180238955

3. SpringCloud Bus動態刷新定點通知

在全局廣播中,我們更新配置文件的資訊對所有服務都進行了通知,但是假設我們只想通知3355,而不想通知3366又該怎麼辦呢,這就需要我們用定點通知的方法通知3355,也就是說我們進行消息通知時只指定具體某個實例生效而不是全部生效,這種情況下就需要修改我們發送的POST請求:

curl -X POST "//{配置中心的地址}/actuator/bus-refresh/{destination}"

這樣的話,actuator/bus-refresh請求就不再發送到具體的服務實例上,而是發給ConfigServer配置中心並通過destination參數指定需要更新配置的服務或實例。其中destination具體為 微服務+埠號。

比如現在我們修改遠端配置文件,但是我們只想通知3355的配置更新資訊,而不想通知3366,此時我們發送的POST請求就應該是:

curl -X POST "//localhost:3344/actuator/bus-refresh/config-client:3355"