Spring Cloud Config 配置中心實踐過程中,你需要了解這些細節!

  • 2019 年 10 月 3 日
  • 筆記

本文導讀:

  • Spring Cloud Config 基本概念
  • Spring Cloud Config 客戶端載入流程
  • Spring Cloud Config 基於消息匯流排配置
  • Spring Cloud Config 中的佔位符
  • Spring Cloud Config 倉庫最佳實踐
  • Spring Cloud Config 健康檢查問題剖析

本文主要介紹 Spring Cloud Config 基本概念、實踐過的配置及遇到的問題進行剖析。關於如何啟動運行配置中心可以參考官方 Demo。

本文使用的Spring Cloud 版本:Edgware.SR3

Spring Cloud Config 基本概念

Spring Cloud Config 用來為分散式系統中的基礎設施和微服務應用提供集中化的外部配置支援。

  • 服務端:分散式配置中心,獨立的微服務應用,用來連接配置倉庫(GIT)並為客戶端提供獲取配置資訊、加密/解密等訪問介面。、
  • 客戶端:微服務架構中各個微服務應用和基礎設施,通過指定配置中心管理應用資源與業務相關的配置內容,啟動時從配置中心獲取和載入配置資訊

SCC作用:

實現了對服務端和客戶端中環境變數和屬性配置的抽象映射。

SCC優勢:

默認採用 GIT 存儲配置資訊,天然支援對配置資訊的版本管理。

Spring Cloud Config架構圖:
精簡架構圖

基於消息匯流排的架構圖:
基於消息匯流排的架構圖

如上圖所示,架構圖中的幾個主要元素作用:
遠程 GIT 倉庫:

用來存儲配置文件的地方。

Config Server:

分散式配置中心,微服務中指定了連接倉庫的位置以及帳號密碼等資訊。

本地 GIT 倉庫:

在 Config Server 文件系統中,客戶單每次請求獲取配置資訊時,Config Server 從 GIT 倉庫獲取最新配置到本地,然後在本地 GIT 倉庫讀取並返回。當遠程倉庫無法獲取時,直接將本地倉庫內容返回。

ServerA/B:

具體的微服務應用,他們指定了 Config Server 地址,從而實現外部化獲取應用自己想要的配置資訊。應用啟動時會向 Config Server 發起請求獲取配置資訊進行載入。

消息中心:

上述第二個架構圖是基於消息匯流排的方式,依賴的外部的 MQ 組件,目前支援 Kafka、Rabbitmq。通過 Config Server 配置中心提供的 /bus/refresh endpoint 作為生產者發送消息,客戶端接受到消息通過http介面形式從 Config Server 拉取配置。

服務註冊中心:

可以將 Config Server 註冊到服務註冊中心上比如 Eureka,然後客戶端通過服務註冊中心發現Config Server 服務列表,選擇其中一台 Config Server 來完成健康檢查以及獲取遠端配置資訊。

Spring Cloud Config 客戶端載入流程

客戶端應用從配置管理中獲取配置執行流程:

1)應用啟動時,根據 bootstrap.yml 中配置的應用名 {application}、環境名 {profile}、分支名 {label},向 Config Server 請求獲取配置資訊。

2)Config Server 根據自己維護的 GIT 倉庫資訊與客戶端傳過來的配置定位去查找配置資訊。

3)通過 git clone 命令將找到的配置下載到 Config Server 的文件系統(本地GIT倉庫)

4)Config Server 創建 Spring 的 ApplicationContext 實例,並從 GIT 本地倉庫中載入配置文件,最後讀取這些配置內容返回給客戶端應用。

5)客戶端應用在獲取外部配置內容後載入到客戶端的 ApplicationContext 實例,該配置內容優先順序高於客戶端 Jar 包內部的配置內容,所以在 Jar 包中重複的內容將不再被載入。

Spring Cloud Config 基於消息匯流排配置

Config Server 作為配置中心 pom.xml 引入:

<dependency>      <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-config-server</artifactId>  </dependency>  <dependency>         <groupId>org.springframework.cloud</groupId>          <artifactId>spring-cloud-starter-bus-kafka</artifactId>  </dependency>  <dependency>          <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-security</artifactId>  </dependency>  <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-actuator</artifactId>  </dependency>

Config Server 配置文件(yml格式):

server:    port: ${CONFIG_SERVER_PORT:8021}  spring:    application:      name: letv-mas-config      # 配置了該項,訪問/bus/refresh?destination=應用名:spring.application.index,如果未指定spring.application.index默認使用「應用名:server.port」      index: ${CONFIG_SERVER_IP:127.0.0.1}    cloud:      config:        server:          git:            # 基於 http 協議的單倉庫,每一個應用創建一個目錄,每個目錄下創建配置文件            uri: ${GIT_URI:http://xxxx/config.git}            search-paths: '{application}'            # 配置的 Git 倉庫基於 http 協議的,必須配置用戶名和密碼            username: ${GIT_USERNAME:config_server}            password: ${GIT_PASSWORD:config@123}            # 本地倉庫目錄設定            basedir: /letv/app/mas/config/repos            # 本地倉庫如果有臟數據,則會強制拉取(默認是false)            force-pull: true            # 配置中心啟動後從 GIT 倉庫下載,如果uri配置中使用了 {application} 作為倉庫名,這裡要使用默認值false,否則啟動報錯.            clone-on-start: false    management:    security:      enabled: false    # 用戶認證,客戶端應用接入時加入安全認證配置  security:    user:      name: config      password: config2018    basic:      enabled: true  # 基於消息匯流排的 MQ 配置  spring:    cloud:      stream:        kafka:          binder:            zk-nodes: ${ZK_NODES:localhost:2181}            brokers: ${KAFKA_BROKERS:localhost:9092}            requiredAcks: -1            configuration:              security:                protocol: SASL_PLAINTEXT              sasl:                mechanism: PLAIN            jaas:              loginModule: org.apache.kafka.common.security.plain.PlainLoginModule              options:                username: test                password: test-secret      # 開啟跟蹤事件消息(默認是false)      bus:        trace:          enabled: true        # 自定義 topic 主題        destination: test.springcloud.config

Config Client 作為客戶端 pom.xml 引入:

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

Config Client 配置文件(yml格式):

spring:    application:      name: letv-mas-client      index: ${CLIENT_SERVER_IP:127.0.0.1}:${server.port}    profiles:      active: ${CLIENT_PROFILE:default}      #include: busdev,streamdev    cloud:      config:        uri: ${CONFIG_SERVER_DOMAIN:http://config.xxx.cn/}        failFast: true #the client will halt with an Exception        enabled: true        # boostrap.yml 配置優先於啟動參數變數 spring.profiles.active        profile: ${spring.profiles.active:${CLIENT_PROFILE:default}}        label: master        # 訪問配置中心,用戶安全認證        username: config        password: config2018        # 激活定時任務,當 GIT 版本發生變更後載入最新配置上下文        watcher:          enabled: true  security:    user:      name: config      password: config2018    # 基於消息匯流排的 MQ 配置( Kakfa 隊列),如果zipkin中也使用 Kafka 隊列,那麼需要通過binder 形式配置做隔離,否則會互相影響,無法下發配置消息。  spring:    cloud:      stream:        # 自定義開關        enabled: true        # 指定中間件        default-binder: config-kafka        binders:          config-kafka:            type: kafka            environment:              spring:                cloud:                  stream:                    kafka:                      binder:                        zkNodes: ${ZK_NODES:localhost:2181}                        brokers: ${KAFKA_BROKERS:localhost:9092}                        # 生產者確認,0、1、-1,默認為1。0為不確認,1為leader單確認,-1為同步副本確認。-1的情況下消息可靠性更高。                        required-acks: -1                        # 是否自動創建topic,默認為true。設為false的情況下,依賴手動配置broker相關topic>配置,如果topic不存在binder則無法啟動。                        auto-create-topics: true                        configuration:                          security:                            protocol: SASL_PLAINTEXT                          sasl:                            mechanism: PLAIN                        jaas:                          loginModule: org.apache.kafka.common.security.plain.PlainLoginModule                          options:                            username: test                            password: test-secret      bus:        # 是否啟用bus        enabled: true        # Bus 使用的隊列或 Topic,Kafka 中的 topic,Rabbitmq 中的 queue        destination: test.springcloud.config        trace:          # 是否啟用 Bus 事件跟蹤,可以通過 /trace 頁面查看          enabled: true        refresh:          # 是否發送 refresh 事件,開啟時支援基於 config 文件變更的動態配置          enabled: true        env:          # 是否開啟 env 事件,開啟時支援直接動態配置相應環境變數,如 /bus/env?arg1=value1&arg2=value2          enabled: true

Spring Cloud Config 中的佔位符

佔位符的使用:

這裡的 {application} 代表了應用名,當客戶端向 Config Server 發起獲取配置請求時,Config Server 會根據客戶端的 spring.application.name 資訊來填充 {application} 佔位符以定位配置資源的存儲位置。

注意:{label} 參數很特別,如果 GIT 分支和標籤包含 「/」,那麼 {label} 參數在 HTTP 的 URL 中應用使用 「(_)」 替代,以避免改變了 URI 含義,指向到其他 URI 資源。

為什麼要有佔位符?

當使用 GIT 作為配置中心來存儲各個微服務應用的配置文件時,URI 中的佔位符的使用可以幫助我們規劃和實現通用的倉庫配置。

Spring Cloud Config 倉庫最佳實踐

本地倉庫:
Spring Cloud 的 D、E 版本中默認存儲到 /var/folders/ml/9rww8x69519fwqlwlt5jrx700000gq/T/config-repo-2486127823875015066目錄下。

在 B 版本中,未實際測試過,存儲到臨時目錄 /tmp/config-repo-隨機數目錄下。
為了避免一些不可預知的問題,我們設置一個固定的本地GIT倉庫目錄。

通過 spring.cloud.config.server.git.basedir=${user.home}/local-config-repo 如果${user.home}目錄下發現local-config-repo不存在,在Config Server啟動後會自動創建,並從GIT遠程倉庫下載配置存儲到這個位置。

遠程倉庫實踐:
單倉庫目錄:
每一個項目對應一個倉庫
spring.cloud.config.server.git.uri=https://gitee.com/ldwds/{application}

多倉庫目錄:
同一個倉庫下,每個項目一個目錄
spring.cloud.config.server.git.uri=https://gitee.com/ldwds/config-repo-demo.git
spring.cloud.config.server.git.search-paths='{application}'

1)單倉庫目錄注意事項:

spring.cloud.config.server.git.uri=[https://gitee.com/ldwds/config-repo-demo/](https://gitee.com/ldwds/config-repo-demo/)    spring.cloud.config.serversearch-paths:』{application}'

客戶端應用啟動前,在 config-repo-demo 倉庫下創建子目錄,子目錄名稱就是配置中指定的spring.application.name 應用名。
否則,工程中引用的屬性找不到,會報如下錯誤:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'from' in value "${from}"

2)多倉庫目錄注意事項:
這種方式不能設置參數
spring.cloud.config.server.git.force-pull=truespring.cloud.config.server.git.clone-on-start=true 
否則啟動會報錯,也很好理解因為使用了 {applicatoin} 作為佔位符,沒有指明具體的倉庫名,所以無法強制拉取遠程倉庫配置。

如果你設置了本地倉庫目錄比如 spring.cloud.config.server.git.basedir=/data/config-repos/local-config-repo Config Server 啟動後會自動創建 /data/config-repos 目錄,並創建 config-repo-隨機數命名的倉庫名錄,這個倉庫下的內容來自於健康檢查的默認倉庫app。

客戶端應用啟動後,會根據 {application} 應用名去查找該倉庫,Config Server 從匹配 Git 倉庫並 clone 到 config-repo-隨機數的目錄下。

如果 Config Server 重啟了,客戶端應用通過 /bus/refresh 刷新配置,因為並沒有快取之前的倉庫名,所以會自動創建一個 config-repo-隨機數 的倉庫目錄並從 Git clone 數據。
如果 Config Server 已有本地倉庫,客戶端重啟或 /bus/refresh 刷新配置則 Config Server 不會重建新的倉庫。

配置中心本地倉庫執行原理:
本地倉庫是否存在根據 basedir 目錄下是否包含.git 隱藏文件。如果本地倉庫不存在,則從遠端倉庫 clone 數據到本地;如果本地倉庫存在,則從遠程倉庫 fetch 最新數據到本地;然後 checkout 到指定 label,從遠端倉庫 merge 數據,並獲取當前 label 分支最新的HEAD 版本,以及默認的應用名 app 作為環境資訊返回。

Spring Cloud Config健康檢查問題剖析

健康檢查 pom.xml 中引入:

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

添加上述依賴後,默認開啟健康檢查。如果不需要健康檢查,可以通過 spring.cloud.config.server.health.enabled=false 參數設定關閉。

如果配置為: spring.cloud.config.server.git.uri=[https://gitee.com/ldwds/config-repo-demo.git](https://gitee.com/ldwds/config-repo-demo.git)
默認開啟了健康檢查,我開始認為默認檢查應用名稱為 app,profiles 為 default,label 為 null進行監控(源碼中看到的)。

但是 GIT 配置倉庫下並沒有 app 應用,此時訪問 /health,監控狀態仍然是 UP?

{      "status": "UP"  }

上述理解是錯誤的,原因分析如下:

這個主要跟配置中心指定的 GIT 倉庫地址有關係,如果倉庫地址指定的是 https://gitee.com/ldwds/{application} ,檢查監視器會將 {application} 替換為默認應用名 app 作為倉庫地址,此時會在 {user.home} 目錄下創建 config-repo-隨機數作為 {application} 應用的本地倉庫(如:/Users/liudewei1228/config-repo-7949870192520306956)。

即使設置了 spring.config.server.git.basedir=${user.home}/local-config-repo/ 也不會被使用到。
為什麼?因為 {application} 作為倉庫,是個動態的,可能會有多個 {application} 項目倉庫,所以不會使用 basedir 特定目錄作為本地倉庫。

如下參數設置健康檢查的配置:

spring.cloud.config.server.health.repositories.config-repo-demo.name=應用名  spring.cloud.config.server.health.repositories.config-repo-demo.label=分支  spring.cloud.config.server.health.repositories.config-repo-demo.profiles=環境變數

找到環境資訊即顯示狀態 UP,此過程出現任何異常(如找不到倉庫 NoSuchRespositoryException)就會顯示 DOWN 狀態。

在uri中包含 {application} 作為倉庫情況下,客戶端應用在啟用前需提前創建好spring.application.name=config-client應用名作為倉庫,否則會導致無法啟用。(因為 {application} 被認為是一個項目倉庫,並不是一個目錄)。

源碼詳見:ConfigServerHealthIndicator.java 的 doHealthCheck 方法。
配置正確倉庫的 name、label、profiles,訪問 /health 介面顯示 sources,這個 sources 中的地址無法訪問的,實際只是一個標識的作用。

訪問/health結果:

{          "status": "UP",          "repositories": [                  {                          "sources": [                                  "https://gitee.com/ldwds/config-repo-demo/config-client/config-client.properties";                          ],                          "name": "config-client",                          "profiles": [                                  "default"                          ],                          "label": "master"                  }          ]  }  

否則,找不到指定倉庫的資訊,只會顯示如下資訊:

{          "status": "UP",          "repositories": [                  {                          "name": "config-client",                          "profiles": [                                  "default"                          ],                          "label": "master"                  }          ]  }

最新 Spring Cloud Config 改進了很多問題,大家可以結合官網進一步學習了解。

本文對 Spring Cloud Config (Spring Cloud E 版本)的基本概念、基於消息匯流排的配置使用、倉庫目錄實踐、健康檢查的實踐以及實踐中遇到的問題進行了剖析,希望有使用到這個配置中心的朋友們有所幫助。

目前微服務架構中選型時,推薦使用中國開源的配置中心:Apollo配置中心(攜程開源)、Nacos註冊&配置中心(阿里巴巴開源)。

歡迎關注我的公眾號,掃碼關注,解鎖更多精彩文章,與你一同成長~
Java愛好者社區