首页 > 技术文章 > SpringCloud Config-分布式配置中心

xjwhaha 2020-12-31 11:30 原文

1. 介绍

分布式微服务系统面临的问题

微服务意味着要将单体应用中的业务拆分成一个个子服务, 每个服务的粒度相对较小,因此系统中会出现大量的服务, 由于每个服务都需要必要的配置信息才能运行, 所以一套集中式动态的 配置管理设施是必不可少的,

例如:

  1. 不同环境,不同配置。例如,数据源配置在不同的环境(开发、测试、预发布、生产等)中是不同的
  2. 运行期间可动态调整。例如,我们可根据各个微服务的负载情况,动态调整数据源连接池大小或熔断阈值,并且在调整配置时不停止微服务
  3. 配置修改后可自动更新。如配置内容发生变化,微服务能够自动更新配置。

而Spring Cloud 就可以顺利解决这个问题,

官方文档: https://docs.spring.io/spring-cloud-config/docs/2.2.5.RELEASE/reference/html/

SpringConfig 简介

Spring Cloud Config为分布式系统外部化配置提供了服务器端和客户端的支持,它包括Config Server和Config Client两部分。

Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环 境下的配置,默认使用Git存储配置内容(也可使用Subversion、本地文件系统或Vault存储 配置),因此可以方便的实现对配置的版本控制与内容审计。 Config Client 是Config Server的客户端,在启动或刷新时读取存储在Config Server中的配置属性,

2. 基本使用

2.1 SpringConfig Server 搭建

Server端也是作为Eureka的Clinet注册进注册中心,

同时他也需要依赖一个配置文件的管理地址,这里我使用gitee,
首先我们需要创建一个仓库,并将本地创建好的不同环境的配置文件写好(必须是UTF-8),并上传到仓库中,入图所示

内容分别是:

  • config-dev.yml

    config:
      info: "master branch,springcloud-config/config-dev.yml version=121" 
    
  • config-prod.yml

    config:
      info: "master branch,springcloud-config/config-prod.yml version=8" 
    
  • config-test.yml

    config:
      info: "master branch,springcloud-config/config-test.yml version=8" 
    

下面开始搭建服务端

pom文件依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Yaml配置文件

server:
  port: 3344
spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/xxxx/springcloud-config.git # 你的git项目地址
          username: username #如果仓库为私有的则需要配置账号密码
          password: password
      label: master #默认master分支

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

主启动类:

@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigCenterMain3344.class, args);
    }
}

启动成功后,测试是否可以访问git, 访问地址:http://127.0.0.1:3344/master/config-dev.yml,成功访问git中对应配置文件信息

访问规则

在上面的测试中,我们使用一个链接,访问到了 配置文件信息, SpringConfig 中提供了多种方式,下面列举常用的几种

  1. 通过{application}-{profiles}.yml来访问
    • http://127.0.0.1:3344/config-dev.yml
    • http://127.0.0.1:3344/config-test.yml
    • http://127.0.0.1:3344/config-prod.yml
  2. 通过/{application}-{profiles}/{lable}
    • http://127.0.0.1:3344/config-test/marter
    • http://127.0.0.1:3344/config-dev/marter
    • http://127.0.0.1:3344/config-prod/marter
  3. 通过/lable/application-{profiles}.yml (推荐)
    • http://127.0.0.1:3344/master/config-dev.yml
    • http://127.0.0.1:3344/master/config-test.yml
    • http://127.0.0.1:3344/master/config-prod.yml

2.2 SpringConfig Clinet 搭建

client 端用于读取 Server中的配置信息,一般为普通的业务微服务

Pom依赖: 和Server端没有区别

bootstrap.yml

因为项目中的配置信息,需要从Config Server 中读取,用来初始化项目中的各个模块,那么Config Server 地址等信息应该在bootstrap.yml

中配置,

  • 此配置文件为引导类配置文件, 优先级最高,并且是在项目启动初始化前就已经被加载,,Spring Cloud 会创建一个 "Bootstrap Context",作为Spring应用的 "Application Context" 的父上下文, 初始化的时候, "Bootstrap Context" 负责从外部源加载配置属性并解析配置, 这两个上下文共享一个外部获取的"Environment"

  • Bootstrap 属性 有高优先级,默认情况下,他们不会被本地配置覆盖, "Bootstrap Context" 和 "Application Context" 有着不同的约定,所以新增一个bootstrap.yml 文件, 保证"Bootstrap Context" 和 "Application Context" 配置分离

所以将Config Server的配置信息 配置在这里才可以被读取

bootstrap.yml信息(放在resources根目录下即可)

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    config:
      label: master # 分支名
      name: config # 配置文件名称
      profile: dev # 环境后缀名称
      uri: http://localhost:3344  # 综上所述, 最终读取的文件为 http://localhost:3344/master/config-dev.yml

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

测试controller,获取从Server端读取的配置信息

@RestController
@RefreshScope //手动刷新
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }

}

启动类

@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3355 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientMain3355.class, args);
    }
}

测试:

浏览器访问接口: http://127.0.0.1:3355/configInfo,返回读取的信息master branch,springcloud-config/config-dev.yml version=121

3. Config客户端动态刷新

此时,当我们模拟运维人员,对git中的配置文件进行修改,并提交

  • 刷新3344,Server端, 发现 Config Server 配置中心立刻响应

  • 刷新3355,Clinet端, 发现 Config Server 配置中心没有刷新

那么,如果修改了配置文件,我们就需要将所有的客户端都进行重启加载,这在实际生产环境中,几乎是不可能的事情,所以我们需要使客户端自动刷新

修改Pom文件,添加额外的依赖(actuator包的功能可以自行百度学习,这里使用了它动态加载配置文件的功能)

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

开启refresh的监控端口

management:
  endpoints:
    web:
      exposure:
        include: "*"   #开启所有的端点

在读取配置文件的类上加上@RefreshScope注解

@RefreshScope
@RestController
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}

测试:

修改dev文件 version改为 130

config:
  info: "master branch,springcloud-config/config-dev.yml version=130" 

再调用client端 接口http://127.0.0.1:3355/actuator/refresh(使用post请求)刷新配置信息, (调用此接口,使用actuator的功能,会清除注解@RefreshScope的缓存以及@ConfigurationProperties 重新加载,也就是将用户自定义使用到的配置文件和Spring Boot项目中各种自动配置的信息都刷新一遍)

再读取client端的信息: http://127.0.0.1:3355/configInfo, 发现已经更新master branch,springcloud-config/config-dev.yml version=130

4. config bus 消息总线支持

在上一节中,我们解决了 修改配置文件,需要重启项目的问题,但是如果微服务过多,那么每次修改配置文件,大量的微服务都需要手动调用请求进行刷新,也是一个比较烦人的工作, 也可以对此进行优化

方式一:

可以利用消息队列的通知方式, 设定一个消息总线方,一般就为 Config Server 端, 设定一个Http 接口, 接口中向指定的队列中发送广播信号, 那么所有Config Server 端只需监听这个队列, 当接受到更新信息后,只需再代码中调用本服务的 Post 刷新接口即可,就可刷新本微服务的配置信息,

如此,当我们修改完配置后,只需调用总线的Http接口,就可自动更新所有Clinet端配置信息

方法二:

使用 消息总线 bus来实现

官方文档:https://spring.io/projects/spring-cloud-bus

原理和上述方案一大致相同, 所以需要依赖 MQ 中间件, 目前仅仅支持 RabbitMq 和 Kafka,下面使用 Rabbitmq演示, 为了演示复杂性,再复制一份客户端, 端口为 3366

Config Server端 和所有需要 Config Clinet 端 添加 相关依赖

    <!--    添加消息总线RabbitMQ支持    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>

同时也都需要配置 RabbitMq的账号地址等信息

server:
  port: 3344
spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/xxxxx/springcloud-config.git
          username: username
          password: password
      label: master #默认master分支
  rabbitmq:
    virtual-host: /test
    host: dev.rabbitmq.md.com
    port: 5672
    username: username
    password: password

但是此时,我们刷新的端点将不是Clinet本身,而是通过 Server端 刷新,并通知给所有客户端,所以Server也要依赖 actuator ,并开放端点

修改Server端配置yml

# 消息总线的支持 这个就是总线 只刷新这个
management:
  endpoints:
    web:
      exposure:
        include: "*" # 开放所有,也可以仅仅开放消息总线的支持 bus-refresh

测试:

修改git中的文件, 并调用 Server 端刷新消息总线接口:http://127.0.0.1:3344/actuator/bus-refresh (Post方式),

这时候我们发现, Clinet端 无须任何操作,配置文件就已经刷新 可以调用接口查询:http://127.0.0.1:3355/configInfo,说明消息总线配置成功,

通过查看RabbitMq的 后台管理页面,可以看见, 在Rabbitmq中自动创建了一个交换机 springCloudBus ,用于通知

单独通知某一个微服务

在上面的案例中,我们刷新后,所有监听mq的客户端都会刷新配置文件,但是当我们想要单独刷新某一个客户端微服务时,也可以使用如下的方式

调用Server端总线接口刷新: http://127.0.0.1:3344/actuator/bus-refresh/{destination},并指定某一个微服务,

例如: http://127.0.0.1:3344/actuator/bus-refresh/config-client:3355, (微服务名:端口的方式), 可以单独刷新某一个微服务

推荐阅读