Spring Cloud Config 實現配置中心,看這一篇就夠了

風的姿態發表於2019-07-25

Spring Cloud Config 是 Spring Cloud 家族中最早的配置中心,雖然後來又釋出了 Consul 可以代替配置中心功能,但是 Config 依然適用於 Spring Cloud 專案,通過簡單的配置即可實現功能。

配置檔案是我們再熟悉不過的了,尤其是 Spring Boot 專案,除了引入相應的 maven 包之外,剩下的工作就是完善配置檔案了,例如 mysql、redis 、security 相關的配置。除了專案執行的基礎配置之外,還有一些配置是與我們業務有關係的,比如說七牛儲存、簡訊相關、郵件相關,或者一些業務上的開關。

對於一些簡單的專案來說,我們一般都是直接把相關配置放在單獨的配置檔案中,以 properties 或者 yml 的格式出現,更省事兒的方式是直接放到 application.properties 或 application.yml 中。但是這樣的方式有個明顯的問題,那就是,當修改了配置之後,必須重啟服務,否則配置無法生效。

目前有一些用的比較多的開源的配置中心,比如攜程的 Apollo、螞蟻金服的 disconf 等,對比 Spring Cloud Config,這些配置中心功能更加強大。有興趣的可以拿來試一試。

接下來,我們開始在 Spring Boot 專案中整合 Spring Cloud Config,並以 github 作為配置儲存。除了 git 外,還可以用資料庫、svn、本地檔案等作為儲存。主要從以下三塊來說一下 Config 的使用。

1.基礎版的配置中心(不整合 Eureka);

2.結合 Eureka 版的配置中心;

3.實現配置的自動重新整理;

實現最簡單的配置中心

最簡單的配置中心,就是啟動一個服務作為服務方,之後各個需要獲取配置的服務作為客戶端來這個服務方獲取配置。

先在 github 中建立配置檔案

我建立的倉庫地址為:配置中心倉庫

目錄結構如下:

Spring Cloud Config 實現配置中心,看這一篇就夠了

配置檔案的內容大致如下,用於區分,略有不同。

data:
  env: config-eureka-dev
  user:
    username: eureka-client-user
    password: 1291029102

注意檔案的名稱不是亂起的,例如上面的 config-single-client-dev.yml 和 config-single-client-prod.yml 這兩個是同一個專案的不同版本,專案名稱為 config-single-client, 一個對應開發版,一個對應正式版。config-eureka-client-dev.yml 和 config-eureka-client-prod.yml 則是另外一個專案的,專案的名稱就是 config-eureka-client 。

建立配置中心服務端

1、新建 Spring Boot 專案,引入 config-server 和 starter-web

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

<!-- spring cloud config 服務端包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

2、配置 config 相關的配置項

bootstrap.yml 檔案

spring:
  application:
    name: config-single-server  # 應用名稱
  cloud:
     config:
        server:
          git:
            uri: https://github.com/huzhicheng/config-only-a-demo #配置檔案所在倉庫
            username: github 登入賬號
            password: github 登入密碼
            default-label: master #配置檔案分支
            search-paths: config  #配置檔案所在根目錄

application.yml

server:
  port: 3301

3、在 Application 啟動類上增加相關注解 @EnableConfigServer

@SpringBootApplication
@EnableConfigServer
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

啟動服務,接下來測試一下。

Spring Cloud Config 有它的一套訪問規則,我們通過這套規則在瀏覽器上直接訪問就可以。

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

{application} 就是應用名稱,對應到配置檔案上來,就是配置檔案的名稱部分,例如我上面建立的配置檔案。

{profile} 就是配置檔案的版本,我們的專案有開發版本、測試環境版本、生產環境版本,對應到配置檔案上來就是以 application-{profile}.yml 加以區分,例如application-dev.yml、application-sit.yml、application-prod.yml。

{label} 表示 git 分支,預設是 master 分支,如果專案是以分支做區分也是可以的,那就可以通過不同的 label 來控制訪問不同的配置檔案了。

上面的 5 條規則中,我們只看前三條,因為我這裡的配置檔案都是 yml 格式的。根據這三條規則,我們可以通過以下地址檢視配置檔案內容:

http://localhost:3301/config-single-client/dev/master

http://localhost:3301/config-single-client/prod

http://localhost:3301/config-single-client-dev.yml

http://localhost:3301/config-single-client-prod.yml

http://localhost:3301/master/config-single-client-prod.yml

通過訪問以上地址,如果可以正常返回資料,則說明配置中心服務端一切正常。

建立配置中心客戶端,使用配置

配置中心服務端好了,配置資料準備好了,接下來,就要在我們的專案中使用它了。

1、引用相關的 maven 包。

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

<!-- spring cloud config 客戶端包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

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

2、初始化配置檔案

bootstrap.yml

spring:
  profiles:
    active: dev

---
spring:
  profiles: prod
  application:
    name: config-single-client
  cloud:
     config:
       uri: http://localhost:3301
       label: master
       profile: prod


---
spring:
  profiles: dev
  application:
    name: config-single-client
  cloud:
     config:
       uri: http://localhost:3301
       label: master
       profile: dev

配置了兩個版本的配置,並通過 spring.profiles.active 設定當前使用的版本,例如本例中使用的 dev 版本。

application.yml

server:
  port: 3302
management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

data:
  env: NaN
  user:
    username: NaN
    password: NaN

其中 management 是關於 actuator 相關的,接下來自動重新整理配置的時候需要使用。

data 部分是當無法讀取配置中心的配置時,使用此配置,以免專案無法啟動。

3、要讀取配置中心的內容,需要增加相關的配置類,Spring Cloud Config 讀取配置中心內容的方式和讀取本地配置檔案中的配置是一模一樣的。可以通過 @Value 或 @ConfigurationProperties 來獲取。

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

4、要讀取配置中心的內容,需要增加相關的配置類,Spring Cloud Config 讀取配置中心內容的方式和讀取本地配置檔案中的配置是一模一樣的。可以通過 @Value 或 @ConfigurationProperties 來獲取。

使用 @Value 的方式:

@Data
@Component
public class GitConfig {

    @Value("${data.env}")
    private String env;

    @Value("${data.user.username}")
    private String username;

    @Value("${data.user.password}")
    private String password;

}

使用 @ConfigurationProperties 的方式:

@Component
@Data
@ConfigurationProperties(prefix = "data")
public class GitAutoRefreshConfig {

    public static class UserInfo {
        private String username;

        private String password;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        @Override
        public String toString() {
            return "UserInfo{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }

    private String env;

    private UserInfo user;
}

4、增加一個 RESTController 來測試使用配置

@RestController
public class GitController {

    @Autowired
    private GitConfig gitConfig;

    @Autowired
    private GitAutoRefreshConfig gitAutoRefreshConfig;

    @GetMapping(value = "show")
    public Object show(){
        return gitConfig;
    }

    @GetMapping(value = "autoShow")
    public Object autoShow(){
        return gitAutoRefreshConfig;
    }
}

5、專案啟動類

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

啟動專案,訪問 RESTful 介面

http://localhost:3302/show,結果如下:

{
  "env": "localhost-dev-edit",
  "username": "fengzheng-dev",
  "password": "password-dev"
}

http://localhost:3302/autoShow,結果如下:

{
  "env": "localhost-dev-edit",
  "user": {
      "username": "fengzheng-dev",
      "password": "password-dev"
    }
}

實現自動重新整理

Spring Cloud Config 在專案啟動時載入配置內容這一機制,導致了它存在一個缺陷,修改配置檔案內容後,不會自動重新整理。例如我們上面的專案,當服務已經啟動的時候,去修改 github 上的配置檔案內容,這時候,再次重新整理頁面,對不起,還是舊的配置內容,新內容不會主動重新整理過來。
但是,總不能每次修改了配置後重啟服務吧。如果是那樣的話,還是不要用它了為好,直接用本地配置檔案豈不是更快。

它提供了一個重新整理機制,但是需要我們主動觸發。那就是 @RefreshScope 註解並結合 actuator ,注意要引入 spring-boot-starter-actuator 包。

1、在 config client 端配置中增加 actuator 配置,上面大家可能就注意到了。

management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

其實這裡主要用到的是 refresh 這個介面

2、在需要讀取配置的類上增加 @RefreshScope 註解,我們是 controller 中使用配置,所以加在 controller 中。

@RestController
@RefreshScope
public class GitController {

    @Autowired
    private GitConfig gitConfig;

    @Autowired
    private GitAutoRefreshConfig gitAutoRefreshConfig;

    @GetMapping(value = "show")
    public Object show(){
        return gitConfig;
    }

    @GetMapping(value = "autoShow")
    public Object autoShow(){
        return gitAutoRefreshConfig;
    }
}

注意,以上都是在 client 端做的修改。

之後,重啟 client 端,重啟後,我們修改 github 上的配置檔案內容,並提交更改,再次重新整理頁面,沒有反應。沒有問題。

接下來,我們傳送 POST 請求到 http://localhost:3302/actuator/refresh 這個介面,用 postman 之類的工具即可,此介面就是用來觸發載入新配置的,返回內容如下:

[
    "config.client.version",
    "data.env"
]

之後,再次訪問 RESTful 介面,http://localhost:3302/autoShow 這個介面獲取的資料已經是最新的了,說明 refresh 機制起作用了。

而 http://localhost:3302/show 獲取的還是舊資料,這與 @Value 註解的實現有關,所以,我們在專案中就不要使用這種方式載入配置了。

在 github 中配置 Webhook

這就結束了嗎,並沒有,總不能每次改了配置後,就用 postman 訪問一下 refresh 介面吧,還是不夠方便呀。 github 提供了一種 webhook 的方式,當有程式碼變更的時候,會呼叫我們設定的地址,來實現我們想達到的目的。

1、進入 github 倉庫配置頁面,選擇 Webhooks ,並點選 add webhook;
Spring Cloud Config 實現配置中心,看這一篇就夠了

2、之後填上回撥的地址,也就是上面提到的 actuator/refresh 這個地址,但是必須保證這個地址是可以被 github 訪問到的。如果是內網就沒辦法了。這也僅僅是個演示,一般公司內的專案都會有自己的程式碼管理工具,例如自建的 gitlab,gitlab 也有 webhook 的功能,這樣就可以呼叫到內網的地址了。

Spring Cloud Config 實現配置中心,看這一篇就夠了

使用 Spring Cloud Bus 來自動重新整理多個端

Spring Cloud Bus 將分散式系統的節點與輕量級訊息代理連結。這可以用於廣播狀態更改(例如配置更改)或其他管理指令。一個關鍵的想法是,Bus 就像一個擴充套件的 Spring Boot 應用程式的分散式執行器,但也可以用作應用程式之間的通訊渠道。

—— Spring Cloud Bus 官方解釋

如果只有一個 client 端的話,那我們用 webhook ,設定手動重新整理都不算太費事,但是如果端比較多的話呢,一個一個去手動重新整理未免有點複雜。這樣的話,我們可以藉助 Spring Cloud Bus 的廣播功能,讓 client 端都訂閱配置更新事件,當配置更新時,觸發其中一個端的更新事件,Spring Cloud Bus 就把此事件廣播到其他訂閱端,以此來達到批量更新。

1、Spring Cloud Bus 核心原理其實就是利用訊息佇列做廣播,所以要先有個訊息佇列,目前官方支援 RabbitMQ 和 kafka。

這裡用的是 RabbitMQ, 所以先要搭一套 RabbitMQ 環境。請自行準備環境,這裡不再贅述。我是用 docker 直接建立的,然後安裝了 rabbitmq-management 外掛,這樣就可以在瀏覽器訪問 15672 檢視 UI 管理介面了。

2、在 client 端增加相關的包,注意,只在 client 端引入就可以。

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

3、在配置檔案中增加 RabbitMQ 相關配置,預設的埠應該是 5672 ,因為我是用 docker 建立的,所以有所不同。

spring:
  rabbitmq:
    host: localhost
    port: 32775
    username: guest
    password: guest

4、啟動兩個或多個 client 端,準備來做個測試

在啟動的時候分別加上 vm option:-Dserver.port=3302 和 -Dserver.port=3303 ,然後分別啟動就可以了。

Spring Cloud Config 實現配置中心,看這一篇就夠了

5、分別開啟 http://localhost:3302/autoShow 和 http://localhost:3303/autoShow,檢視內容,然後修改 github 上配置檔案的內容並提交。再次訪問這兩個地址,資料沒有變化。

6、訪問其中一個的 actuator/bus-refresh 地址,注意還是要用 POST 方式訪問。之後檢視控制檯輸出,會看到這兩個端都有一條這樣的日誌輸出

o.s.cloud.bus.event.RefreshListener: Received remote refresh request. Keys refreshed

7、再次訪問第 5 步的兩個地址,會看到內容都已經更新為修改後的資料了。

綜上所述,當我們修改配置後,使用 webhook ,或者手動觸發的方式 POST 請求一個 client 端的 actuator/bus-refresh 介面,就可以更新給所有端了。

結合 Eureka 使用 Spring Cloud Config

以上講了 Spring Cloud Config 最基礎的用法,但是如果我們的系統中使用了 Eureka 作為服務註冊發現中心,那麼 Spring Cloud Config 也應該註冊到 Eureka 之上,方便其他服務消費者使用,並且可以註冊多個配置中心服務端,以實現高可用。

好的,接下來就來整合 Spring Cloud Config 到 Eureka 上。

在 github 倉庫中增加配置檔案

Spring Cloud Config 實現配置中心,看這一篇就夠了

啟動 Eureka Server

首先啟動一個 Eureka Server,之前的文章有講過 Eureka ,可以回過頭去看看。Spring Cloud Eureka 實現服務註冊發現,為了清楚,這裡還是把配置列出來

1、pom 中引入相關包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2、設定配置檔案內容

bootstrap.yml

spring:
  application:
    name: kite-eureka-center
  security:
      user:
        name: test  # 使用者名稱
        password: 123456   # 密碼
  cloud:
    inetutils: ## 網路卡設定
      ignoredInterfaces:  ## 忽略的網路卡
        - docker0
        - veth.*
        - VM.*
      preferredNetworks:  ## 優先的網段
        - 192.168

application.yml

server:
  port: 3000
eureka:
  instance:
    hostname: eureka-center
    appname: 註冊中心
  client:
    registerWithEureka: false # 單點的時候設定為 false 禁止註冊自身
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://test:123456@localhost:3000/eureka
  server:
    enableSelfPreservation: false
    evictionIntervalTimerInMs: 4000

3、Application 啟動類

@EnableEurekaServer
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4、啟動服務,在瀏覽器訪問 3000 埠,並輸出使用者名稱 test,密碼 123456 即可進入 Eureka UI

配置 Spring Cloud Config 服務端

服務端和前面的相比也就是多了註冊到 Eureka 的配置,其他地方都是一樣的。

1、在 pom 中引入相關的包

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

<!-- spring cloud config 服務端包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

<!-- eureka client 端包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、配置檔案做配置

application.yml

server:
  port: 3012
eureka:
  client:
    serviceUrl:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://test:123456@localhost:3000/eureka/
  instance:
    preferIpAddress: true
spring:
  application:
    name: config-eureka-server
  cloud:
     config:
        server:
          git:
            uri: https://github.com/huzhicheng/config-only-a-demo
            username: github 使用者名稱
            password: github 密碼
            default-label: master
            search-paths: config

相比於不加 Eureka 的版本,這裡僅僅是增加了 Eureka 的配置,將配置中心註冊到 Eureka ,作為服務提供者對外提供服務。

3、啟動類,在 @EnableConfigServer 的基礎上增加了 @EnableEurekaClient,另外也可以用 @EnableDiscoveryClient 代替 @EnableEurekaClient

@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4、啟動服務,之後訪問 Eureka ,可以看到服務已註冊成功

Spring Cloud Config 實現配置中心,看這一篇就夠了

配置 Spring Cloud Config 客戶端

客戶端的配置相對來說變動大一點,加入了 Eureka 之後,就不用再直接和配置中心服務端打交道了,要通過 Eureka 來訪問。另外,還是要注意客戶端的 application 名稱要和 github 中配置檔案的名稱一致。

1、pom 中引入相關的包

<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.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、配置檔案

bootstrap.yml

eureka:
  client:
    serviceUrl:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://test:123456@localhost:3000/eureka/
  instance:
    preferIpAddress: true


spring:
  profiles:
    active: dev

---
spring:
  profiles: prod
  application:
    name: config-eureka-client
  cloud:
     config:
       label: master
       profile: prod
       discovery:
         enabled: true
         service-id: config-eureka-server


---
spring:
  profiles: dev
  application:
    name: config-eureka-client
  cloud:
     config:
       label: master  #指定倉庫分支
       profile: dev   #指定版本 本例中建立了dev 和 prod兩個版本
       discovery:
          enabled: true  # 開啟
          service-id: config-eureka-server # 指定配置中心服務端的server-id 

除了註冊到 Eureka 的配置外,就是配置和配置中心服務端建立關係。

其中 service-id 也就是服務端的application name。

application.yml

server:
  port: 3011
management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

data:
  env: NaN
  user:
    username: NaN
    password: NaN

3、啟動類,增加了 @EnableEurekaClient 註解,可以用 @EnableDiscoveryClient 代替

@SpringBootApplication
@EnableEurekaClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4、另外的配置實體類和 RESTController 和上面的一樣,沒有任何更改,直接參考即可。

5、啟動 client 端,訪問 http://localhost:3011/autoShow,即可看到配置檔案內容。

這個例子只是介紹了和 Eureka 結合的最基礎的情況,還可以註冊到高可用的 Eureka 註冊中心,另外,配置中心服務端還可以註冊多個例項,同時保證註冊中心的高可用。

注意事項

1. 在 git 上的配置檔案的名字要和 config 的 client 端的 application name 對應;

2. 在結合 eureka 的場景中,關於 eureka 和 git config 相關的配置要放在 bootstrap.yml 中,否則會請求預設的 config server 配置,這是因為當你加了配置中心,服務就要先去配置中心獲取配置,而這個時候,application.yml 配置檔案還沒有開始載入,而 bootstrap.yml 是最先載入的。

如果你覺得寫的還可以的話,請點個「推薦」吧

歡迎關注,不定期更新本系列和其他文章
古時的風箏 ,進入公眾號可以加入交流群
Spring Cloud Config 實現配置中心,看這一篇就夠了

相關文章