Spring Cloud分佈式配置中心:Config+Bus

目錄

1 Git倉庫

2 配置服務端

2.1 pom.xml

2.2 啓動類

2.3 application.properties

2.4 運行及結果

3 配置客戶端

3.1 pom.xml

3.2 啓動類

3.3 bootstrap.properties

3.4 Controller

3.5 運行及結果


Spring Cloud Config是一個解決分佈式系統的配置管理方案,同時Spring Cloud支持Git或Svn來存放配置文件,默認爲Git。Spring Cloud Config分爲服務端和客戶端兩個部分,其中服務端也被稱爲分佈式配置中心。所以整體的流程是服務端從Git遠程倉庫中克隆配置文件並存到服務端,然後以接口的形式將配置文件的內容提供出去,客戶端通過接口獲取數據,然後依據此數據初始化自己的應用。

本文中Spring Cloud Config會被配置成服務化、配置數據動態刷新和請求失敗重試的功能。服務化可以使服務端和客戶端耦合度降低,動態刷新可以在不重啓客戶端的情況下,動態刷新配置數據,而請求失敗重試可以保證在網絡情況不好的情況下的服務可用性。其中Spring Cloud Config會整合Spring Cloud Bus,以此來實現服務端發送更新請求到Spring Cloud Bus,其收到消息後再自動通知所有客戶端的效果,減少更新成本。本文中使用RabbitMQ消息代理作爲通知通道。

筆者使用的Java版本是jdk-8u201,IDE使用的是IntelliJ IDEA 2019.2 x64,Spring Boot的版本是2.1.7.RELEASE,Spring Cloud的版本是Greenwich.SR2。同時本文所使用的項目代碼沿用筆者之前寫過的文章《Spring Cloud服務治理:Eureka+OpenFeign(Ribbon+Hystrix)》中的項目代碼,並在此基礎上進行繼續開發。


1 Git倉庫

首先需要在GitHub上創建一個遠程倉庫,命名爲testConfig(創建過程很簡單,不會的可自行在網上查看,這裏不再贅述)。然後需要創建一個本地倉庫:本地創建一個文件夾,命名爲testConfig,其中再創建一個文件夾,命名爲client1。client1中存放着三個配置文件,分別爲client1-dev.properties、client1-prod.properties和client1-test.properties,用來演示開發、生產和測試環境的配置文件。這三個文件中都只有一個相同的配置項,如下所示:

client1-dev.properties:

my.config=dev

client1-prod.properties:

my.config=prod

client1-test.properties:

my.config=test

接着打開Git Bash,將當前路徑切換到testConfig的目錄下,然後輸入下面的命令,將本地的配置文件上傳到剛剛配好的GitHub遠程倉庫中:

git init
git add .
git commit -m "微服務配置"
git remote add origin https://github.com/fake/testConfig.git
git push -u origin master

其中第四條命令中的遠程倉庫地址是個假地址,讀者可自行替換成自己的git倉庫地址即可。

上傳完畢後的效果如下所示:


2 配置服務端

首先創建一個Spring Boot項目,命名爲config-server。

2.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hys</groupId>
    <artifactId>config-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>config-server</name>
    <description>Demo project for Spring Cloud</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

其中除了服務端的依賴之外,spring-cloud-starter-netflix-eureka-client依賴是爲了與Eureka服務註冊中心進行整合,以此來實現服務化。而spring-cloud-starter-bus-amqp依賴是爲了啓用Spring Cloud Bus。

2.2 啓動類

package com.hys.configserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }

}

其中@EnableConfigServer註解用來開啓Spring Cloud Config的服務端功能。

2.3 application.properties

spring.application.name=config-server
server.port=7001
spring.cloud.config.server.git.uri=https://github.com/fake/testConfig.git
spring.cloud.config.server.git.search-paths=client1
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.password=password
eureka.client.service-url.defaultZone=http://localhost:1111/eureka,http://localhost:1112/eureka,http://localhost:1113/eureka
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=root
management.endpoints.web.exposure.include=bus-refresh

spring.cloud.config.server.git.uri配置項表示GitHub遠程倉庫的地址,數據將會從這裏加載(這裏是假地址,需要讀者自行替換成自己的GitHub倉庫地址);

spring.cloud.config.server.git.search-paths配置項表示倉庫中配置文件的地址,是個相對位置;

spring.cloud.config.server.git.username和spring.cloud.config.server.git.password配置項是自己的GitHub的用戶名和密碼,如果遠程倉庫是公開的,那麼可以不用填寫用戶名和密碼,如果是私有的倉庫則必須填寫;

eureka.client.service-url.defaultZone配置項是Eureka服務註冊中心的地址;

spring.rabbitmq.host、spring.rabbitmq.port、spring.rabbitmq.username、spring.rabbitmq.password配置項是RabbitMQ的一些連接配置,這裏用來實現Spring Cloud Bus的消息代理;

management.endpoints.web.exposure.include配置項在這裏是用來暴露/actuator/bus-refresh接口,請求該接口會使服務端向Spring Cloud Bus發送請求,Spring Cloud Bus接收到請求後再向所有的客戶端更新配置信息。

2.4 運行及結果

我們可以先單獨啓動服務端來查看結果。確保之前搭建的eureka-server項目處於運行狀態,然後在Postman中訪問http://localhost:7001/client1/dev/master,結果如下所示:

可以看到返回了我們在client1-dev.properties中的配置項內容,此時我們修改本地的client1-dev.properties內容:

my.config=dev123

將原來的dev改爲dev123,再提交到GitHub上去,確保遠程倉庫中的內容發生了改變,這時我們再次訪問http://localhost:7001/client1/dev/master,查看結果:

可以看到,配置項的值已經改爲了dev123,由此可見服務端是能夠實現配置內容動態刷新的效果。


3 配置客戶端

首先創建一個Spring Boot項目,命名爲config-client。

3.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hys</groupId>
    <artifactId>config-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>config-client</name>
    <description>Demo project for Spring Cloud</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

其中除了客戶端的依賴之外,spring-boot-starter-web依賴是用來開啓Spring MVC及Web支持的,spring-cloud-starter-netflix-eureka-client依賴是爲了與Eureka服務註冊中心進行整合,以此來實現服務化。spring-retry和spring-boot-starter-aop依賴則是用來支持請求失敗重試的功能。spring-cloud-starter-bus-amqp依賴是爲了啓用Spring Cloud Bus。

3.2 啓動類

package com.hys.configclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConfigClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class, args);
    }

}

3.3 bootstrap.properties

客戶端的配置文件是放在bootstrap.properties而不是application.properties中,在resources目錄下手工創建一個即可。bootstrap.properties文件的加載時機更早,優先級更高。

spring.application.name=client1
server.port=7002
spring.cloud.config.profile=dev
spring.cloud.config.label=master
spring.cloud.config.discovery.service-id=config-server
spring.cloud.config.discovery.enabled=true
eureka.client.service-url.defaultZone=http://localhost:1111/eureka,http://localhost:1112/eureka,http://localhost:1113/eureka
spring.cloud.config.fail-fast=true
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=root
eureka.instance.instance-id=${spring.application.name}:${server.port}

spring.application.name配置項的值不能隨便寫,其和{application}-{profile}.properties配置文件中的{application}一致。我們這裏是client1-dev.properties,所以該值必須爲client1;

spring.cloud.config.profile配置項的值和{application}-{profile}.properties配置文件中的{profile}一致,表示應用的環境;

spring.cloud.config.label配置項對應於{label},表示Git上的分支;

pring.cloud.config.discovery.service-id配置項表示配置Spring Cloud Config服務端的ServiceId,客戶端通過這個值在Eureka註冊中心中查找服務端的信息;

spring.cloud.config.discovery.enabled配置項爲true表示開啓通過Eureka獲取Spring Cloud Config服務端的功能;

eureka.client.service-url.defaultZone配置項是Eureka服務註冊中心的地址;

spring.cloud.config.fail-fast配置項爲true表示開啓請求失敗重試;

spring.rabbitmq.host、spring.rabbitmq.port、spring.rabbitmq.username、spring.rabbitmq.password配置項是RabbitMQ的一些連接配置,這裏用來實現Spring Cloud Bus的消息代理;

eureka.instance.instance-id配置項是爲了給每一個客戶端一個實例id,是由服務名:端口號組成。這裏是用來模擬Spring Cloud Bus逐個刷新的效果。

3.4 Controller

package com.hys.configclient.controller;

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

@RefreshScope
@RestController
public class HelloController {

    @Value("${my.config}")
    private String hello;

    @GetMapping("/hello")
    public String hello() {
        return hello;
    }
}

hello方法讀取當前配置文件中my.config的值並返回。

3.5 運行及結果

保證之前的eureka-server和config-server項目處於運行狀態,同時保證RabbitMQ處於運行中。將本項目打包後,分別執行下面的命令來啓動兩個客戶端:

java -jar config-client-0.0.1-SNAPSHOT.jar
java -jar config-client-0.0.1-SNAPSHOT.jar --server.port=7003

啓動完成後,在Eureka的註冊中心中查看得到以下的結果:

一個服務端,兩個客戶端。首先我們在頁面中訪問http://localhost:7002/hellohttp://localhost:7003/hello,結果如下:

由上可知,之前我們在服務端的演示中,將該配置項的值改爲了dev123。現在我們將client1-dev.properties中的內容再做些修改:

my.config=dev

現在又改回了dev。然後將改變上傳到GitHub遠程倉庫中。在Postman中訪問http://localhost:7001/actuator/bus-refresh,注意請求是Post請求:

我們向服務端發送了/actuator/bus-refresh請求,該請求會發送到Spring Cloud Bus中,並最終更新到所有的客戶端。這時我們再在頁面中訪問http://localhost:7002/hellohttp://localhost:7003/hello,結果如下:

可以看到,兩個客戶端的配置都已經改爲了dev,客戶端的動態刷新是成功的。如果我們不用Spring Cloud Bus的話,那麼就得在每個客戶端中都請求一次/refresh接口,如果客戶端很多的話,維護起來負擔也是非常大。所以這就是Spring Cloud Bus的意義,只需要向服務端請求一次刷新接口,Spring Cloud Bus就能將所有客戶端都進行更新。

同時我們可以在頁面上訪問http://localhost:15672,查看RabbitMQ的Web管理界面,查看到Spring Cloud Bus已經創建了一個名爲springCloudBus的交換器以及一些相關的隊列:

除了支持通知所有的客戶端之外,Spring Cloud Bus還支持逐個客戶端的通知更新操作。只需要向服務端輸入如下的訪問http://localhost:7001/actuator/bus-refresh/client1:7003即可:

以上操作的效果是隻對7003端口的客戶端做更新通知,而7002端口的客戶端不會收到通知。在輸入上述url之前我們先將本地配置文件中該項的值再次改爲dev123,然後上傳到GitHub。接着再輸入上面只對7003端口的客戶端進行刷新通知的url。最後在頁面中訪問http://localhost:7002/hellohttp://localhost:7003/hello,以驗證結果:

由上面可以看到,正如我們預想的那樣,只有7003端口的客戶端成功刷新了配置,7002端口的客戶端沒有刷新配置,驗證成功。

最後來驗證請求失敗重試的效果。如果沒有請求失敗重試並且請求失敗的原因只是因爲網絡波動等其他間歇性原因導致的,那麼直接啓動失敗似乎代價有些高,所以從這種角度來說請求失敗重試還是很有意義的。我們現在將服務端和客戶端都停掉,只啓動客戶端代碼,以此來模擬請求失敗的情況。查看後臺打印的日誌:

2019-08-13 06:07:55.156  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001/
2019-08-13 06:07:57.195  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001/. Will be trying the next url if available
2019-08-13 06:07:58.197  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001/
2019-08-13 06:08:00.213  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001/. Will be trying the next url if available
2019-08-13 06:08:01.315  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001/
2019-08-13 06:08:03.331  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001/. Will be trying the next url if available
2019-08-13 06:08:04.543  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001/
2019-08-13 06:08:06.561  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001/. Will be trying the next url if available
2019-08-13 06:08:07.895  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001/
2019-08-13 06:08:09.912  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001/. Will be trying the next url if available
2019-08-13 06:08:11.378  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001/
2019-08-13 06:08:13.395  INFO 16680 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001/. Will be trying the next url if available
2019-08-13 06:08:13.401 ERROR 16680 --- [           main] o.s.boot.SpringApplication               : Application run failed

其中可以看到有如上所示的內容,請求一共發送了6次,重試了5次,這是默認的策略,可以自行更改。請求失敗重試可以避免一些間歇性問題引起的失敗導致客戶端應用無法啓動的情況出現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章