SpringCloud系列--Spring Cloud Config

其他網址

Spring Cloud Config 實現配置中心,看這一篇就夠了 - 風的姿態 - 博客園

另見《SpringCloud微服務實戰》
              => 第8章 分佈式配置中心:Spring Cloud Config
              => 第9章 消息總線:Spring Cloud Bus

簡介

        配置文件是我們再熟悉不過的了,尤其是 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.實現最簡單的Spring Cloud Config(不集成 Eureka);

2.結合 Eureka 使用 Spring Cloud Config;

3.實現配置的自動刷新;

實現最簡單的Spring Cloud Config

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

先在 github 中建立配置文件

我創建的倉庫地址爲:配置中心倉庫

目錄結構如下:

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

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"
  	}
}

結合 Eureka 使用 Spring Cloud Config

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

好的,接下來就來集成 Spring Cloud Config 到 Eureka 上。

在 github 倉庫中增加配置文件

啓動 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 客戶端

客戶端的配置相對來說變動大一點,加入了 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 在項目啓動時加載配置內容這一機制,導致了它存在一個缺陷,修改配置文件內容後,不會自動刷新。例如我們上面的項目,當服務已經啓動的時候,去修改 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;

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

使用 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 ,然後分別啓動就可以了。

5、分別打開 http://localhost:3302/autoShowhttp://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 接口,就可以更新給所有端了。

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