Spring Boot + Spring Cloud 構建微服務系統(十):配置中心(Spring Cloud Bus)


技術背景

我們在上一篇講到,Spring Boot程序只在啟動的時候加載配置文件信息,這樣在GIT倉庫配置修改之后,雖然配置中心服務器能夠讀取最新的提交信息,但是配置中心客戶端卻不會重新讀取,以至於不能及時的讀取更新后的配置信息。這個時候就需要一種通知刷新機制來支持了。

Refresh機制

refresh機制是Spring Cloud Config提供的一種刷新機制,它允許客戶端通過POST方法觸發各自的/refresh,只要依賴spring-boot-starter-actuator包就擁有了/refresh的功能,下面我們為我們的客戶端加上刷新功能,以支持更新配置的讀取。

添加依賴

修改 spring-cloud-conifg-client,添加監控依賴,監控依賴包里攜帶了 /refresh 的功能。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

開啟更新機制

在使用配置屬性的類型加上 @RefreshScope 注解,這樣在客戶端執行 /refresh 的時候就會刷新此類下面的配置屬性了。

package com.louis.spring.cloud.config.client.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RefreshScope
@RestController
class HelloController {
    
    @Value("${spring.config.hello}")
    private String hello;

    @RequestMapping("/hello")
    public String from() {
        return this.hello;
    }
}

修改配置

修改配置文件添加以下內容,開放refresh的相關接口。

bootstrap.yml

management:
  endpoints:
    web:
      exposure:
        include: "*"

這樣,以后以post請求的方式訪問 http://localhost:8552/actuator/refresh 時,就會更新修改后的配置文件了。

特別注意:

這里存在着版本大坑,1.x跟2.x的配置不太一樣,我們用的是2.0+版本,務必注意。

1.安全配置變更

新版本

management.endpoints.web.exposure.include="*"

老版本

management.security.enabled=false

2.訪問地址變更

新版本

http://localhost:8552/actuator/refresh

老版本

http://localhost:8552/refresh

這里還是解釋一下上面這個配置起到了什么具體作用,其實actuator是一個健康檢查包,它提供了一些健康檢查數據接口,refresh功能也是其中的一個接口,但是為了安全起見,它默認只開放了health和info接口(啟動信息會包含如下圖所示信息),而上面的配置就是設置要開放哪些接口, 我們設置成 “*”,是開放所有接口。你也可以指定開發幾個,比如: health,info,refresh,而這里因為我們需要用的refresh功能,所以需要把refresh接口開放出來。

 

設置成 “*” 后,啟動信息會包含以下信息,而這個叫refresh的post方法,就是我們需要的,上面說的接口地址變更從這里也可以看得出來。

測試效果

訪問 http://localhost:8552/hello,返回結果如下。

修改倉庫配置內容,把數字2改成5,如下圖所示。

再次訪問 http://localhost:8552/hello,如我們所料,結果並沒有更新,因為我們還沒有調refresh方法。

 

通過工具或自寫代碼發送post請求 http://localhost:8552/actuator/refresh,刷新配置。

這里通過在線測試網站發送,地址:https://getman.cn/Mo2FX

注意:先讓你的Chrome支持跨域。設置方法:在快捷方式的target后加上 --disable-web-security --user-data-dir,重啟即可。

刷新之后,再次訪問 http://localhost:8552/hello,返回結果如下。

查看返回結果,刷新之后已經可以獲取最新提交的配置內容,但是每次都需要手動刷新客戶端還是很麻煩,如果客戶端數量一多就簡直難以忍受了,有沒有什么比較好的辦法來解決這個問題呢,那是當然的,答案就是:Spring Cloud Bus。

Spring Cloud Bus

Spring Cloud Bus,被大家稱為消息總線,它通過輕量級的消息代理來連接各個分布的節點,可以利用像消息隊列的廣播機制在分布式系統中進行消息傳播,通過消息總線可以實現很多業務功能,其中對於配置中心客戶端刷新,就是一個非常典型的使用場景。

下面這張圖可以很好的解釋消息總線的作用流程(圖片描述來源:純潔的微笑:配置中心博文)。

Spring Cloud Bus 進行配置更新步驟如下:

  1、提交代碼觸發post請求給/actuator/bus-refresh

  2、server端接收到請求並發送給Spring Cloud Bus

  3、Spring Cloud bus接到消息並通知給其它客戶端

  4、其它客戶端接收到通知,請求Server端獲取最新配置

  5、全部客戶端均獲取到最新的配置

安裝RabbitMQ

因為我們需要用到消息隊列,我們這里選擇RabbitMQ,使用Docker進行安裝。

拉取鏡像

執行以下命令,拉取鏡像。

docker pull rabbitmq:management

完成之后執行以下命令查看下載鏡像。

docker images

創建容器

執行以下命令,創建docker容器。

docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management

啟動成功之后,可以執行以下命令查看啟動容器。

docker ps

登錄界面

容器啟動之后就可以訪問web管理界面了,訪問 http://宿主機IP:15672。

系統提供了默認賬號。 用戶名:guest  密碼: guest

管理界面

客戶端實現

添加依賴

打開客戶端 spring-cloud-conifg-client,添加相關依賴。

pom.xml

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

修改配置

修改配置,添加RebbitMq的相關配置,這樣客戶端代碼就改造完成了。

bootstrap.yml

spring:
  application:
    name: spring-cloud-config-client
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        serviceName: ${spring.application.name}    # 注冊到consul的服務名稱
    config:
      discovery:
        enabled: true
        serviceId: spring-cloud-config-server # 配置中心服務名稱
      name: spring-config  # 對應{application}部分
      profile: dev  # 對應{profile}部分
      label: master  # 對應git的分支,如果配置中心使用的是本地存儲,則該參數無用
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
management:
  endpoints:
    web:
      exposure:
        include: "*"

服務端實現

添加依賴

修改 spring-cloud-conifg-server,添加相關依賴。

pom.xml

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

修改配置,添加RebbitMq的和接口開放相關配置,這樣服務端代碼也改造完成了。

application.yml

server:
  port: 8551
spring:
  application:
    name: spring-cloud-config-server
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        serviceName: ${spring.application.name}    # 注冊到consul的服務名稱
    config:
      server:
        git:
          uri: https://gitee.com/liuge1988/spring-cloud-demo/     # 配置git倉庫的地址
          search-paths: config-repository                             # git倉庫地址下的相對地址,可以配置多個,用,分割。
          username: username                                             # git倉庫的賬號
          password: password                                             # git倉庫的密碼
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
management:
  endpoints:
    web:
      exposure:
        include: "*"

測試效果

1.啟動服務端,成功集成消息總線后,啟動信息中可以看到如下圖中的信息。

 2.啟動客戶端,發現居然報錯了,網上也找不到相關資料,也沒見其他人提過相關問題。猜測是網上教程多是使用Euraka,而這里用的時Consul,瞎鼓搗了好久,反正是不想換回Euraka,2.0停止開發消息出來以后,將來還不定什么情況,只能硬着頭皮解決了。

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'configServerRetryInterceptor' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:685) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1210) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.getDelegate(AnnotationAwareRetryOperationsInterceptor.java:180) ~[spring-retry-1.2.2.RELEASE.jar:na]
    at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:151) ~[spring-retry-1.2.2.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.cloud.config.client.ConfigServerInstanceProvider$$EnhancerBySpringCGLIB$$dd44720b.getConfigServerInstances(<generated>) ~[spring-cloud-config-client-2.0.0.RELEASE.jar:2.0.0.RELEASE]
    at org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration.refresh(DiscoveryClientConfigServiceBootstrapConfiguration.java:84) [spring-cloud-config-client-2.0.0.RELEASE.jar:2.0.0.RELEASE]
    at org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration.startup(DiscoveryClientConfigServiceBootstrapConfiguration.java:69) [spring-cloud-config-client-2.0.0.RELEASE.jar:2.0.0.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]

然后就跟蹤代碼,發現是在下圖中的位置找不到相應的Bean,那么答案就比較明顯了,要么是程序有BUG,不過可能性不大,那應該是就是缺包了,在缺失的包里有這個Bean。但是這個Bean是在哪個包?排查了半天也沒找到,網上也沒有想過資料,對比了一下網上消息總線的配置,依賴也沒有少加什么。

 

沒有辦法,最后只能自己上手了,不就是在刷新的時候缺少一個攔截器嗎,自己給他弄一個試試唄。

使用就加了一個配置類,並在resources下新建了META-INF目錄和一個spring。factories文件。

 

RetryConfiguration.java

package com.louis.spring.cloud.config.client;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.interceptor.RetryInterceptorBuilder;
import org.springframework.retry.interceptor.RetryOperationsInterceptor;


public class RetryConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "configServerRetryInterceptor")
    public RetryOperationsInterceptor configServerRetryInterceptor() {
        return RetryInterceptorBuilder.stateless().backOffOptions(1000, 1.2, 5000).maxAttempts(10).build();
    }
}

spring.factories

org.springframework.cloud.bootstrap.BootstrapConfiguration=com.louis.spring.cloud.config.client.RetryConfiguration

在這里指定新建的攔截器,這樣系統初始化時會加載這個Bean。

然后重啟啟動,果然沒有報錯了,還是先別高興,看看能不能用先。

4.先訪問一下 http://localhost:8552/hello,效果如下圖所示。

5.修改倉庫配置文件,把數字5改成15,修改完成提交。

再次訪問發現還是舊信息。

6.再用工具發送post請求 http://localhost:8551/actuator/bus-refresh 。

注意這次是向注冊中心服務端發送請求,發送成功之后服務端會通過消息總線通知所有的客戶端進行刷新。

另外開啟消息總線后的請求地址是 /actuator/bus-refresh,不再是refresh了。

 7.給服務端發送刷新請求之后,再次訪問 http://localhost:8552/hello,結果如下。

我們愉快的發現客戶端已經能夠通過消息總線獲取最新配置了,真是可喜可賀。

 

源碼下載

碼雲:https://gitee.com/liuge1988/spring-cloud-demo.git


作者:朝雨憶輕塵
出處:https://www.cnblogs.com/xifengxiaoma/ 
版權所有,歡迎轉載,轉載請注明原文作者及出處。


免責聲明!

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



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