本文導讀:
- 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=true
和 spring.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注冊&配置中心(阿里巴巴開源)。
歡迎關注我的公眾號,掃碼關注,解鎖更多精彩文章,與你一同成長~