Spring Cloud(八):使用Spring Cloud Bus來實現配置動態更新


使用Spring Cloud Config我們能實現服務配置的集中化管理,在服務啟動時從Config Server獲取需要的配置屬性。但如果在服務運行過程中,我們需要將某個配置屬性進行修改,比如將驗證碼的失效時間從五分鍾調整為十分鍾,如何將這個更新在服務端不重啟服務就能動態生效,是本文討論的內容。

Spring Cloud Bus

Spring Cloud Bus可以理解為Spring Cloud體系架構中的消息總線,通過一個輕量級的Message Broker來將分布式系統中的節點連接起來。可用來實現廣播狀態更新(如配置更新),或其它管理指令。
Spring Cloud Bus 就像是一個分布式的Spring Boot Actuator, 目前提供了兩種類型的消息隊列中間件支持:RabbitMQ與Kafka(對應的pom依賴分別為spring-cloud-starter-bus-amqp, spring-cloud-starter-bus-kafka)。

Spring Cloud 在spring-cloud-context中添加了兩個actuator管理接口(POST請求): /actuator/env/actuator/refresh, 前者可用於更新當前服務實例Environment對象中的配置屬性,后者可用於刷新當前服務實例的配置信息。

Spring Cloud Bus也提供了兩個對應的接口

  1. /actuator/bus-env,相對於/actuator/env , 使用鍵值對更新每個實例的Environment,默認不暴露,需配置management.endpoints.web.exposure.include=bus-env 來開放接口訪問
  2. /actuator/bus-refresh,相對於/actuator/refresh,對每個實例,清空RefreshScope緩存,重新綁定@ConfigurationProperties, 默認不暴露,可通過配置
    management.endpoints.web.exposure.include=bus-refresh 來開放接口訪問

綜上,/actuator/env/actuator/refresh 是針對單個服務實例修改或刷新其配置信息,而 /actuator/bus-env/actuator/bus-refresh 則是借助於Spring Cloud Bus的消息機制作用於分布式系統中的所有服務實例,因此前面有Spring Cloud Bus 就像是一個分布式的Spring Boot Actuator的說法。

使用Spring Cloud Bus來實現服務配置動態更新的結構圖如下

spring-cloud-bus

  1. 更新配置倉庫中的配置文件,push到遠程Git倉庫
  2. 遠程Git倉庫通過Webhook調用配置服務器的通知更新接口
  3. 配置服務器發送配置更新消息到消息總線
  4. 其它服務節點監聽到配置服務器發送的配置更新消息
  5. 其它服務節點向配置服務器發送拉取最新配置的請求
  6. 配置服務器向配置倉庫拉取最新的配置返回給其它服務節點

案例演示

我們還是以前面的springcloud-config, springcloud-eureka, springcloud-eureka-client三個項目來完成本文的案例演示。源碼地址

使用Actuator

在不引入Spring Cloud Bus的情況下,我們可以通過Spring Cloud提供的actuator接口來實現單個實例的配置動態更新。

依次啟動springcloud-eureka, springcloud-config, springcloud-eureka-client項目,然后修改springcloud-eureka-client的啟動端口,將8080改為8081,再啟動一個springcloud-eureka-client的服務實例。

springcloud-eureka-client 的測試接口代碼如下

@RestController
@RefreshScope
public class HelloController {

    @Autowired
    private Environment env;

    @Value("${app}")
    private String app;

    @RequestMapping("/hello")
    public String hello(){
        return "Hello, welcome to spring cloud 2. env: " + env.getProperty("app") + ", value: " + app;
    }
}

此時依次請求兩個實例的hello接口,得到結果如下

springcloudbus-test1

springcloudbus-test2

我們通過/actuator/env接口來修改端口8080實例的屬性app的值,使用postman操作如圖

springcloudbus-test3

此時再請求接口返回結果如下

springcloudbus-test4

可以看到Environment對象中app屬性的值已更新,但是 @Value注解的屬性值未變,可見 /actuator/env 接口只是更新了Environment對象,並不負責刷新其它方式引用的屬性值。此時請求另一個端口為8081的實例接口,其屬性值都未更新,也可見 /actuator/env 只作用於當前實例本身。

如果要讓8080實例的@Value屬性也動態更新,則可再調用/actuator/refresh接口,如圖

springcloudbus-test5

此時再請求測試接口,得到結果如下(@Value注解的屬性也已經更新了)

springcloudbus-test6

使用Spring Cloud Bus

前面我們使用 /actuator/env/actuator/refresh 兩個接口可以實現單個服務實例配置的動態更新,但在微服務架構中,服務實例可能達幾十甚至幾百個,一個個調用來做動態更新就有點太不方便了。這時就該Spring Cloud Bus登場了。

1.添加依賴與配置

在springcloud-config, 與springcloud-eureka-client兩個項目中,添加spring cloud bus的依賴與配置。
在pom.xml文件中添加依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

在application.yml配置文件中添加RabbitMQ的相關配置

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: rabbitmq
    password: passw0rd

2.依次啟動springcloud-eureka, springcloud-config, springcloud-eureka-client項目,並以8081端口再啟動一個springcloud-eureka-client的服務實例。

3.我們使用postman對配置服務器調用/actuator/bus-env接口,

springcloudbus-test7

請求兩個服務實例的測試接口,得到結果

springcloudbus-test9

springcloudbus-test8

兩個實例的Environment對象都已經更新,如果要將@Value注解的屬性也更新,則可再調用配置服務器的/actuator/bus-refresh接口。

/actuator/bus-env接口是直接更新的內存Environment實例屬性,如果服務重啟,則又還原到之前的配置了, 所以還是需要借助配置倉庫來永久更新。配置更新后還需要手動調用接口使其生效?DevOps時代了,能自動化的就自動化吧,我們可以借助Git的webhook機制來實現自動化。

自動化

本文開頭的“使用Spring Cloud Bus來實現服務配置動態更新的結構圖”已經示例了使用Git倉庫的webhook來觸發自動更新配置的流程。但是在Git(如Github)中,我們不能直接使用/actuator/bus-refresh接口來作為webhook(因為接口協議不一致,會出現解析異常),也有人通過提供自己的接口來作為webhook,在自己接口中再轉發請求到/actuator/bus-refresh來實現。但實際上,spring-cloud-config-monitor已經提供了對Git webhook的支持。

如下圖,spring-cloud-config-monitor提供了對Github,Gitlab,Gitee,BitBucket等的支持

springcloud-config-monitor

1.在配置服務器springcloud-config的pom.xml文件中添加依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-monitor</artifactId>
</dependency>

2.在配置倉庫的設置頁面配置webhook,比如Github的配置如圖

config-github-webhook

Payload URL 配置為配置服務器的monitor接口地址,path參數必須。如果你的配置服務器在內網,比如做本地測試時,還需要實現一下內網穿透(如frp)。
在配置倉庫項目中修改配置屬性,提交代碼,Github webhook就會觸發自動更新,上圖下方紅色框為觸發自動更新的記錄。

自動更新配置未生效排查

如果出現Github觸發了自動更新,但服務的配置更新未生效的情況,則需要查看webhook的匹配規則與服務實例的ServiceID是否匹配,webhook的匹配規則為 spring.application.name:spring.cloud.config.profile:**,服務實例的ServiceID可通過spring.cloud.bus.id配置,如果沒有配置,則默認為

${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}}

遵循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,否則給一個隨機值

我們可以在服務項目中打開spring cloud bus的debug日志

logging:
  level:
    org.springframework.cloud.bus: debug

通過DefaultBusPathMatcher的debug日志來查看是否匹配,如

DEBUG 286196 --- [7O8XC9KNWbyDA-1] o.s.cloud.bus.DefaultBusPathMatcher      : In match: hello-service:8081:c96f04c81dfce6dffaa9d116811d127c, hello-service:8081:c96f04c81dfce6dffaa9d116811d127c

如果沒有匹配則可以按照webhook的匹配規則設置spring.cloud.bus.id值或vcap.application.instance_index值,如

spring:
  application:
    name: hello-service
  cloud:
    config:
      discovery:
        service-id: config-server
        enabled: true
      profile: ${spring.profiles.active:default}
    bus:
      id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}
#或
vcap:
  application:
    instance_index: ${spring.cloud.config.profile}

配置更新未生效的另一個情況是查看是否用了@RefreshScope注解。

@RefreshScope

細心的人會發現本文開頭的測試接口類上加了@RefreshScope注解。 @RefreshScope是Spring Cloud提供的用來實現配置、實例熱加載的注解。被@RefreshScope修飾的@Bean都是延遲加載的,即在第一次訪問(調用方法)時才會被初始化,並且這些bean存於緩存中。當收到配置更新的消息時,緩存中的@RefreshScope bean會被清除,這樣下次訪問時將會重新創建bean,此時使用的就是最新的配置信息,從而實現配置的熱加載。

總結

本文分別示例了使用spring boot actuator與spring cloud bus來實現服務配置的更新及兩者之間的區別, spring cloud bus一定程度上像是一個分布式的spring boot actuator。同時演示了使用webhook與spring cloud bus,monitor結合來實現配置自動更新的具體流程及可能遇到的問題。


認真生活,快樂分享!如果你覺得文章對你有幫助,歡迎分享轉發!
歡迎關注微信公眾號:空山新雨的技術空間
獲取Spring Boot,Spring Cloud,Docker等系列技術文章
公眾號二維碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM