一、目前急需解决的问题

在分布式微服务系统中,几乎所有服务的运行都离不开配置文件的支持,这些配置文件通常由各个服务自行管理,以 properties 或 yml 格式保存在各个微服务的类路径下,例如 application.properties 或 application.yml 等。

这种将配置文件散落在各个服务中的管理方式,存在以下问题:

  • 管理难度大:配置文件散落在各个微服务中,难以管理。
  • 安全性低:配置跟随源代码保存在代码库中,容易造成配置泄漏。
  • 时效性差:微服务中的配置修改后,必须重启服务,否则无法生效。
  • 局限性明显:无法支持动态调整,例如日志开关、功能开关。

为了解决这些问题,通常我们都会使用配置中心对配置进行统一管理。Spring Cloud 中提供了分布式配置中心 Spring Cloud Config。

二、Spring Cloud Config

Spring Cloud Config 是由 Spring Cloud 团队开发的项目,为微服务架构中各个微服务提供集中化的外部配置支持。Spring Cloud Config 可以将各个微服务的配置文件集中存储在一个外部的存储仓库或系统(例如 Git 、SVN 等)中,对配置的统一管理,以支持各个微服务的运行。

Spring Cloud Config 包含以下两个部分:

  • Config Server:也被称为分布式配置中心,它是一个独立运行的微服务应用,用来连接配置仓库并为客户端提供获取配置信息、加密信息和解密信息的访问接口。
  • Config Client:指的是微服务架构中的各个微服务,它们通过 Config Server 对配置进行管理,并从 Config Sever 中获取和加载配置信息。

Spring Cloud Config 默认使用 Git 存储配置信息,因此使用 Spirng Cloud Config 构建的配置服务器天然就支持对微服务配置的版本管理。我们可以使用 Git 客户端工具方便地对配置内容进行管理和访问。除了 Git 外,Spring Cloud Config 还提供了对其他存储方式的支持,例如 SVN、本地化文件系统等。

三、Spring Cloud Config 工作原理

Config工作原理如下图所示:

SpringCloud config分布式配置中心_Cloud

Spring Cloud Config 工作流程如下:

  1. 开发或运维人员提交配置文件到远程的 Git 仓库。
  2. Config 服务端(分布式配置中心)负责连接配置仓库 Git,并对 Config 客户端暴露获取配置的接口。
  3. Config 客户端通过 Config 服务端暴露出来的接口,拉取配置仓库中的配置。
  4. Config 客户端获取到配置信息,以支持服务的运行。

四、Spring Cloud Config 的特点

Spring Cloud Config 具有以下特点:

  • Spring Cloud Config 是 Spring Cloud 官方团队开发,能够与 Spring 的生态体系无缝集成。
  • Spring Cloud Config 将所有微服务的配置文件集中存储在一个外部的存储仓库或系统(例如 Git)中,统一管理。
  • Spring Cloud Config 配置中心将配置以 REST 接口的形式暴露给各个微服务,以方便各个微服务获取。
  • 微服务可以通过 Spring Cloud Config 向配置中心统一拉取属于它们自己的配置信息。
  • 当配置发生变化时,微服务不需要重启即可感知到配置的变化,并自动获取和应用最新配置。
  • 一个应用可能有多个环境,例如开发(dev)环境、测试(test)环境、生产(prod)环境等等,开发人员可以通过 Spring Cloud Config 对不同环境的各配置进行管理,且能够确保应用在环境迁移后仍然有完整的配置支持其正常运行。

五、Config服务端配置

5.1.在 Github 上创建一个名为 springcloud-config 的仓库(Repository)并获取该仓库的地址。

由于 Github 站点在国内使用网络原因,存在加载缓慢等问题,因此建议在​​码云​​上创建仓库

5.2.新建Module模块cloud-config-center-3344

该模块作为为Cloud的配置中心模块

SpringCloud config分布式配置中心_spring_02

设置模块名:

SpringCloud config分布式配置中心_微服务_03

5.3.在pom中引入依赖

依赖内容如下:

<dependencies>
<!--Config Server 指的是服务端-->
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.augus.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>

5.4.创建配置文件

在resources目录下创建:application.yml

server:
port: 3344 #端口号
spring:
application:
name: spring-cloud-config-center #服务名
cloud:
config:
server:
git:
# Git 地址,https://gitee.com/hello/springcloud-config.git
# 码云(gitee)地址 uri: https://github.com/hello/springcloud-config.git (github 站点访问较慢,因此这里我们使用 gitee)
uri: https://gitee.com/haugus/springcloud-config.git
#仓库名
search-paths:
- springcloud-config
force-pull: true
# 如果Git仓库为公开仓库,可以不填写用户名和密码,如果是私有仓库需要填写
username: gitee账户
password: 密码
#分支名
label: master
eureka:
client: #将客户端注册到 eureka 服务列表内
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

5.5.创建主启动类

在com.augus.cloud 包下创建主题启动类 :ConfigCenterMain3344 注意需要添加@EnableConfigServer 指定为Config的服务端

package com.augus.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

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

5.6.新建一个名为 config-dev.yml 的文件,并将其上传到 springcloud-config 仓库 master 分支下,config-dev.yml 的内容如下。

config:
info: www.augus.com
version: 1.0

5.7.启动7001服务注册中心和 cloud-config-center-3344

通过config微服务配置中心测试是否可以从GitHub上获取配置内容,浏览器访问:http://localhost:3344/master/config-dev.yml,如下图所示:

SpringCloud config分布式配置中心_spring_04

 Spring Cloud Config 规定了一套配置文件访问规则,如下表。

访问规则

示例

/{application}/{profile}[/{label}]

/config/dev/master

/{application}-{profile}.{suffix}

/config-dev.yml

/{label}/{application}-{profile}.{suffix}

/master/config-dev.yml

访问规则内各参数说明如下。

  • {application}:应用名称,即配置文件的名称,例如 config-dev。
  • {profile}:环境名,一个项目通常都有开发(dev)版本、测试(test)环境版本、生产(prod)环境版本,配置文件则以 application-{profile}.yml 的形式进行区分,例如 application-dev.yml、application-test.yml、application-prod.yml 等。
  • {label}:Git 分支名,默认是 master 分支,当访问默认分支下的配置文件时,该参数可以省略,即第二种访问方式。
  • {suffix}:配置文件的后缀,例如 config-dev.yml 的后缀为 yml。

通过上面的规则 在浏览器上访问“http://localhost:3344/config-dev.yml”,使用默认的分支直接省略,结果如下图。

SpringCloud config分布式配置中心_微服务_05

 在浏览器上访问“http://localhost:3344/config/dev/master”,结果如下:

SpringCloud config分布式配置中心_spring_06

六、搭建 Config 客户端

6.1.新建cloud-config-center-3355模块

创建maven模块:

SpringCloud config分布式配置中心_微服务_07

设置模块名:

SpringCloud config分布式配置中心_微服务_08

6.2.在pom中引入依赖

下面的主要是需要有springcloud Config 的客户端依赖

<dependencies>
<!--config client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</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-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.augus.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>

6.3.创建配置文件

这里需要在resources目录下创建 bootstrap.yml 文件说明:

加载顺序

若application.yml 和bootstrap.yml 在同一目录下:bootstrap.yml 先加载 application.yml后加载,bootstrap.yml 用于应用程序上下文的引导阶段。bootstrap.yml 由父Spring ApplicationContext加载。

配置区别

  • bootstrap是spring cloud的配置上下文加载。由spring-cloud-content包加载。引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
  • application是spring boot的配置加载。引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

文件内容如下:

#bootstrap.yml 是系统级别的,加载优先级高于 application.yml ,负责从外部加载配置并解析
server:
port: 3355 #端口号
spring:
application:
name: spring-cloud-config-client #服务名
cloud:
config:
label: master #分支名称
name: config #配置文件名称,config-dev.yml 中的 config
profile: dev #环境名 config-dev.yml 中的 dev
#这里不要忘记添加 http:// 否则无法读取
uri: http://localhost:3344 #Spring Cloud Config 服务端(配置中心)地址
eureka:
client: #将客户端注册到 eureka 服务列表内
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #将服务注册到 Eureka 集群

6.4.创建主启动类

在com.augus.cloud包中创建主启动类:CloudConfigClient3355,使用 @EnableEurekaClient 注解开启 Eureka 客户端功能 内容如下:

package com.augusu.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

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

6.5.在 com.augus.cloud.controller 包下,创建一个名为 ConfigClientController 的类

 通过该类读取配置文件中的配置,代码如下。

package com.augusu.cloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigClientController {

//获取配置文件中端口
@Value("${server.port}")
private String serverPort;

//读取配置文件中info对应的值
@Value("${config.info}")
private String configInfo;

//读取配置文件中info对应的值
@Value("${config.version}")
private String configVersion;

@GetMapping(value = "/getConfig")
public String getConfig(){
return "服务的端口:"+serverPort+"</br>info:"+configInfo+"</br>version:"+configVersion;
}
}

6.6.启动cloud-config-client-3355

使用浏览器访问 http://localhost:3355/getConfig 结果如下图

SpringCloud config分布式配置中心_spring_09

6.7.将配置文件 config-dev.yml 中 config.version 的值修改为 2.0

配置如下:

SpringCloud config分布式配置中心_Cloud_10

6.8.依次启动 Eureka 服务注册中心7001\7002和 cloud-config-center-3344 服务配置中心,

使用浏览器访问 http://localhost:3344/master/config-dev.yml ,结果如下图。

SpringCloud config分布式配置中心_微服务_11

从上图可以看出,配置中心已经成功地获取到了修改后的配置。

6.9.再次访问 http://localhost:3355/getConfig,

尝试通过 Spring Cloud Config 客户端获取修改后的配置信息,结果如下图。

SpringCloud config分布式配置中心_Cloud_12

会发现如上图所示,3355客户端没有重启的时候,获取的还是修改之前的信息

6.10.重启 cloud-config-client-3355 客户端服务

再次使用访问 http://localhost:3355/getConfig ,结果如下图。

SpringCloud config分布式配置中心_Cloud_13

 通过该实例,我们可以得到以下 2 点结论,

  • 配置修改更新后,Spring Cloud Config Server  服务端 可以直接从 Git 仓库中获取最新的配置。
  • 除非重启 Spring Cloud Config 客户端(Client),否则无法通过 Spring Cloud Config 服务端获取最新的配置信息

七、手动刷新配置

为解决不重启 Config 客户端无法获取最新配置的问题,对需要对 cloud-config-client-3355 进行改造,改造步骤如下。

7.1.在 cloud-config-client-3355 的 pom.xml 中添加以下依赖

引入 Spring Boot actuator 监控模块:

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

7.2.在配置文件 bootstrap.yml 中添加以下配置

对外暴露 Spring Boot actuator 的监控节点。

# Spring Boot 2.50对 actuator 监控屏蔽了大多数的节点,只暴露了 health 节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号

7.3.在修改ConfigClientController 类

使用 @RefreshScope 注解开启配置刷新,代码如下

package com.augusu.cloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope
public class ConfigClientController {

//获取配置文件中端口
@Value("${server.port}")
private String serverPort;

//读取配置文件中info对应的值
@Value("${config.info}")
private String configInfo;

//读取配置文件中info对应的值
@Value("${config.version}")
private String configVersion;

@GetMapping(value = "/getConfig")
public String getConfig(){
return "服务的端口:"+serverPort+"</br>info:"+configInfo+"</br>version:"+configVersion;
}
}

7.4.重启 cloud-config-client-3355客户端服务

然后将配置文件 config-dev.yml 中的 config.version 修改为 3.0,配置如下。

SpringCloud config分布式配置中心_Cloud_14

7.5.使用浏览器再次访问 http://localhost:3355/getConfig

结果如下图:

SpringCloud config分布式配置中心_Cloud_15

 会发现我们通过上面的设置对 Spring Cloud Config 客户端进行了改造,也依然无法直接获取到最新配置,还是之前的2.0

7.6.上述的问题如何解决呢?

我们需要打开命令行窗口,使用以下命令发送一个 POST 请求刷新 Spring Cloud Config 3355 客户端,通知客户端配置文件已经修改,需要重新拉去配置。

curl -X POST "http://localhost:3355/actuator/refresh"

7.7.再次使用浏览器访问 http://localhost:3355/getConfig

结果如下图,可以发现修改后的配置信息已经刷新过来了

SpringCloud config分布式配置中心_spring_16

八、手动刷新配置引发的问题

在上面的案例中,通过 Config 3355客户端中引入 Spring Boot actuator 监控组件来监控配置的变化,可以在不重启 Config 客户端的情况下获取到了最新配置,原理如下图。

SpringCloud config分布式配置中心_spring_17

上面虽然解决了重启 Config 客户端才能获取最新配置的问题,但却出现了另一个问题,只要配置仓库中的配置发生改变,就需要向每一个 Config 客户端手动发送 POST 请求,通知它们重新拉取配置。所谓的 Config 客户端其实就是一个一个的服务。在微服务架构中,一个系统往往包含几十甚至上百个个服务,如果因为某一个配置文件的修改而向几十个微服务发送 POST 请求,这显然是很困难的。所以需要做到 “一次通知,处处生效”的方式。在Spring Cloud Config 中可以配合 Bus 实现配置的动态刷新。