Spring Cloud Config 配置中心实践过程中,你需要了解这些细节!

2019 Java 开发者跳槽指南.pdf (吐血整理)….>>>

本文导读:

  • 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架构图:Spring Cloud Config 配置中心实践过程中,你需要了解这些细节!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注册&配置中心(阿里巴巴开源)。

END

 

原文始发于微信公众号(Java爱好者社区):Spring Cloud Config 配置中心实践过程中,你需要了解这些细节!