Spring Cloud Config配置中心剖析(一)

本文主要介绍Spring Cloud Config基本概念、实践过的配置及遇到的问题进行分析,关于如何启动运行配置中心可以参考官方Demo。

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

单仓库目录注意事项:

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=truespring.cloud.config.server.git.clone-on-start=true
否则启动会报错,也很好理解因为使用了{applicatoin}作为占位符,没有指明具体的仓库名,所以无法强制拉取远程仓库配置。如果你设置了本地仓库目录比如spring.cloud.config.server.git.basedir=/data/config-repos/local-config-repoConfig 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的基本概念、基于消息总线的配置使用、仓库目录实践、健康检查的实践以及实践中遇到的问题进行了剖析,如果有疑问欢迎留言讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容