本文主要介绍Spring Cloud Config基本概念、实践过的配置及遇到的问题进行分析,关于如何启动运行配置中心可以参考官方Demo。
Spring Cloud Config基本概念
Spring Cloud Config用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持。
- 服务端:分布式配置中心,独立的微服务应用,用来连接配置仓库(GIT)并为客户端提供获取配置信息、加密/解密等访问接口。、
- 客户端:微服务架构中各个微服务应用和基础设施,通过指定配置中心管理应用资源与业务相关的配置内容,启动时从配置中心获取和加载配置信息
SCC作用:
实现了对服务端和客户端中环境变量和属性配置的抽象映射。
SCC优势:
Spring Cloud Config架构图:默认采用GIT存储配置信息,天然支持对配置信息的版本管理。
如上图所示,架构图中的几个主要元素作用:
远程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}'
单仓库目录注意事项:
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}"
多仓库目录注意事项:
这种方式不能设置参数
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不会重建新的仓库。
配置中心本地仓库(缺省用Git仓库)执行原理
本地仓库是否存在根据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的基本概念、基于消息总线的配置使用、仓库目录实践、健康检查的实践以及实践中遇到的问题进行了剖析,如果有疑问欢迎留言讨论。