先回顧一下,在之前的Spring Cloud Config的介紹中,我們還留了一個懸念:如何實現對配置資訊的實時更新。雖然,我們已經能夠通過/refresh介面和Git倉庫的Web Hook來實現Git倉庫中的內容修改觸發應用程式的屬性更新。但是,若所有觸發操作均需要我們手工去維護Web Hook中的應用位置的話,這隨著系統的不斷擴張,會變的越來越難以維護,而訊息代理中介軟體是解決該問題最為合適的方案。是否還記得我們在介紹訊息代理中的特點時有提到過這樣一個功能:訊息代理中介軟體可以將訊息路由到一個或多個目的地。利用這個功能,我們就能完美的解決該問題,下面我們來說說Spring Cloud Bus中的具體實現方案。

《Spring Boot中使用RabbitMQ》一文中,我們已經介紹了關於訊息代理、AMQP協議以及RabbitMQ的基礎知識和使用方法。下面我們開始具體介紹Spring Cloud Bus的配置,並以一個Spring Cloud Bus與Spring Cloud Config結合的例子來實現配置內容的實時更新。

RabbitMQ實現

下面我們來具體動手嘗試整個配置過程:

  • 準備工作:這裡我們不做新的應用,但需要用到上一章中,我們已經實現的關於Spring Cloud Config的幾個工程,若讀者對其還不瞭解,建議先閱讀第4章的內容。
    • config-repo:定義在Git倉庫中的一個目錄,其中儲存了應用名為didispace的多環境配置檔案,配置檔案中有一個from引數。
    • config-server-eureka:配置了Git倉庫,並註冊到了Eureka的服務端。
    • config-client-eureka:通過Eureka發現Config Server的客戶端,應用名為didispace,用來訪問配置伺服器以獲取配置資訊。該應用中提供了一個/from介面,它會獲取config-repo/didispace-dev.properties中的from屬性返回。
  • 擴充套件config-client-eureka應用
    • 修改pom.xml增加spring-cloud-starter-bus-amqp模組(注意spring-boot-starter-actuator模組也是必須的)。
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
  • 在配置檔案中增加關於RabbitMQ的連線和使用者資訊
1
2
3
4
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=springcloud
spring.rabbitmq.password=123456
  • 啟動config-server-eureka,再啟動兩個config-client-eureka(分別在不同的埠上,比如7002、7003),我們可以在config-client-eureka中的控制檯中看到如下內容,在啟動時候,客戶端程式多了一個/bus/refresh請求。
1
o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/bus/refresh],methods=[POST]}" onto public void org.springframework.cloud.bus.endpoint.RefreshBusEndpoint.refresh(java.lang.String)
  • 先訪問兩個config-client-eureka的/from請求,會返回當前config-repo/didispace-dev.properties中的from屬性。
  • 接著,我們修改config-repo/didispace-dev.properties中的from屬性值,併發送POST請求到其中的一個/bus/refresh
  • 最後,我們再分別訪問啟動的兩個config-client-eureka的/from請求,此時這兩個請求都會返回最新的config-repo/didispace-dev.properties中的from屬性。

到這裡,我們已經能夠通過Spring Cloud Bus來實時更新總線上的屬性配置了。

原理分析

我們通過使用Spring Cloud Bus與Spring Cloud Config的整合,並以RabbitMQ作為訊息代理,實現了應用配置的動態更新。

整個方案的架構如上圖所示,其中包含了Git倉庫、Config Server、以及微服務“Service A”的三個例項,這三個例項中都引入了Spring Cloud Bus,所以他們都連線到了RabbitMQ的訊息總線上。

當我們將系統啟動起來之後,“Service A”的三個例項會請求Config Server以獲取配置資訊,Config Server根據應用配置的規則從Git倉庫中獲取配置資訊並返回。

此時,若我們需要修改“Service A”的屬性。首先,通過Git管理工具去倉庫中修改對應的屬性值,但是這個修改並不會觸發“Service A”例項的屬性更新。我們向“Service A”的例項3傳送POST請求,訪問/bus/refresh介面。此時,“Service A”的例項3就會將重新整理請求傳送到訊息匯流排中,該訊息事件會被“Service A”的例項1和例項2從匯流排中獲取到,並重新從Config Server中獲取他們的配置資訊,從而實現配置資訊的動態更新。

而從Git倉庫中配置的修改到發起/bus/refresh的POST請求這一步可以通過Git倉庫的Web Hook來自動觸發。由於所有連線到訊息總線上的應用都會接受到更新請求,所以在Web Hook中就不需要維護所有節點內容來進行更新,從而解決了通過Web Hook來逐個進行重新整理的問題。

指定重新整理範圍

上面的例子中,我們通過向服務例項請求Spring Cloud Bus的/bus/refresh介面,從而觸發總線上其他服務例項的/refresh。但是有些特殊場景下(比如:灰度釋出),我們希望可以重新整理微服務中某個具體例項的配置。

Spring Cloud Bus對這種場景也有很好的支援:/bus/refresh介面還提供了destination引數,用來定位具體要重新整理的應用程式。比如,我們可以請求/bus/refresh?destination=customers:9000,此時總線上的各應用例項會根據destination屬性的值來判斷是否為自己的例項名,若符合才進行配置重新整理,若不符合就忽略該訊息。

destination引數除了可以定位具體的例項之外,還可以用來定位具體的服務。定位服務的原理是通過使用Spring的PathMatecher(路徑匹配)來實現,比如:/bus/refresh?destination=customers:**,該請求會觸發customers服務的所有例項進行重新整理。

架構優化

既然Spring Cloud Bus的/bus/refresh介面提供了針對服務和例項進行配置更新的引數,那麼我們的架構也相應的可以做出一些調整。在之前的架構中,服務的配置更新需要通過向具體服務中的某個例項傳送請求,再觸發對整個服務叢集的配置更新。雖然能實現功能,但是這樣的結果是,我們指定的應用例項就會不同於叢集中的其他應用例項,這樣會增加叢集內部的複雜度,不利於將來的運維工作,比如:我們需要對服務例項進行遷移,那麼我們不得不修改Web Hook中的配置等。所以我們要儘可能的讓服務叢集中的各個節點是對等的。

因此,我們將之前的架構做了一些調整,如下圖所示:

我們主要做了這些改動:

  1. 在Config Server中也引入Spring Cloud Bus,將配置服務端也加入到訊息匯流排中來。
  2. /bus/refresh請求不在傳送到具體服務例項上,而是傳送給Config Server,並通過destination引數來指定需要更新配置的服務或例項。

通過上面的改動,我們的服務例項就不需要再承擔觸發配置更新的職責。同時,對於Git的觸發等配置都只需要針對Config Server即可,從而簡化了叢集上的一些維護工作。

本文完整示例:

 本文由 程式猿DD-翟永超 創作,採用 CC BY 3.0 CN協議 進行許可。 可自由轉載、引用,但需署名作者且註明文章出處。