Spring Cloud Config分布式配置中心
分布式系统面临的配置文件问题
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的!
SpringCloud 提供了 ConfigServer 来解决这个问题,我们每一个微服务自己带着一个 application.yml ,上百个配置文件的管理…
Spring Cloud Config 简介
Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环节提供了一个中心化的外部配置。
上图所示,假如有三个配置文件,然后每一个配置文件都连接了数据库(同一个),这个时候如果我要换一个数据库使用,那么就要将所有配置文件中的配置进行替换,这个时候我们就可以使用 Config Server 来进行统一配置,这样在我们管理的时候也会比较简单!
Spring Cloud Config 分为服务端和客户端两部分。
服务端 也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密 信息等访问接口。
客户端 则是通过指定的配置中心来管理应用的资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载信息配置服务器默认采用 git 来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过 git 客户端工具来方便的管理和访问配置内容。
可以做的操作
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署比如 dev/test/prod/beta/release
- 运行期间动态调整配置,不在需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉去配置自己的信息。
- 当配置发生改变的时候,服务不需要重启即可感知到配置文件的变化并且应用新的配置
- 将配置信息以 REST 接口的形式暴露(post、curl 访问刷新均可)
由于 Spring Cloud Config 默认使用 Git 来存储配置文件(也有其它方式,比如支持 SVN 和 本地文件),但最推荐的还是 Git,而且使用的是 http/https 访问的形式!
服务端配置与测试
-
在 github上创建一个名为 spring-cloud-config 的仓库
-
将创建好的 git 仓库配置在本地
使用 git clone 命令将 git 仓库下载下来
git clone "自己的仓库地址"
仓库地址:
-
新建以下三个文件
config-dev.yaml
config: info: master branch,springcloud-config/config-dev.yaml version=1
config-prod.yaml
config: info: master branch,springcloud-config/config-prod.yaml version=1
config-test.yaml
config: info: master branch,springcloud-config/config-test.yaml version=1
然后将这三个文件全部放进下载的本地仓库中
文件的名字一定不要乱取,要安装官网的规则来定
-
输入以下命令将命令将新建的文件上传至 git 仓库
4.1:进入配置好的本地仓库中
4.2:查看工作目录和暂存区的状态
这里我们就可以看到我们刚才放进去的三个新的文件
4.3:使用 git add --all 提交未跟踪、修改和删除文件
4.4:这一步是第一次使用该仓库时需要进行的操作
//获取git邮箱 git config --global user.email "git邮箱" //获取git账号名 git config --global user.name "git用户名"
这里需要注意的是,这里填写的邮箱和用户名都必须和 github 中的一致
4.5:再次执行 git commit -m “init yaml”
4.6:提交数据至 github 仓库
4.7:查看是否提交成功
刚开始创建好项目的时候,只有一个 .md 文件,当我们提交成功之后就会把我们的文件存入仓库中
新建模块
创建一个名为 cloud-config-center-3344 的服务(这个服务就相当于 Config Server)
修改 pom 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloud2020</artifactId>
<groupId>com.lyang.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-center-3344</artifactId>
<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>
<!-- 引入自己定义的api通用包,可以使用 Payment 支付 Entity -->
<dependency>
<groupId>com.lyang.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
编写 yml 配置文件
server:
port: 3344
spring:
application:
# 注册进 eureka 服务器的微服务名
name: cloud-config-center
cloud:
config:
server:
git:
# 自己的 github 仓库地址
uri: https://github.com/T257ymq/spring-cloud-config.git
# 搜索目录
search-paths:
- spring-cloud-config
# 读取分支
label: master
# 服务注册到 eureka 地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
编写主启动类
/**
* 3344启动类
* @EnableConfigServer:启用配置服务器
*/
@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class, args);
}
}
配置读取
方式一:/{lobel}/{application}-{profile}.yaml(推荐)
这种方式可以指定分支进行访问
方式二:/{application}-{profile}.yml
这种方式没有指定分支的路径使用,所以就会直接使用默认的分支进行访问!
方式三:/{application}-{profile}[/{label}]
这种方式的话需要我们自己去解析它里面的内容
属性说明
label:分支(branch)
name:服务名
profiles:环境(dev/test/prod)
客户端配置与测试
创建一个名为 cloud-config-client-3355 的子工程
修改 pom 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloud2020</artifactId>
<groupId>com.lyang.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-3355</artifactId>
<dependencies>
<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>
<!-- 引入自己定义的api通用包,可以使用 Payment 支付 Entity -->
<dependency>
<groupId>com.lyang.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
编写 bootstrap.yml 文件
server:
port: 3355
spring:
application:
name: config-client
cloud:
# Config 客户端配置
config:
# 分支名称
label: master
# 配置文件名称
name: config
# 读取后缀名称
profile: dev
# 配置中心地址
uri: http://localhost:3344
# 服务注册到 Eureka 地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
label、name、profile综合: master 分支上 config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yaml
这里与之前有所不同,因为这里使用的是 bootstrap 格式的命名方式,而我们在前面使用的都是application 的命名方式来编写配置文件!
Spring Cloud 会创建一个 “Bootstrap Context”,作为 Spring 应用的 Application Context 的父上下文。初始化的时候 Bootstrap Context 负责从外部源加载配置属性并解析配置,这两个上下文共享一个从外部获取的 Environment。
bootetrap.yml 文件的优先级比 application.yml 文件要高!
使用这个文件主要是为了更好的配置加载顺序和分级管理!
3355 不会直接连接到git仓库,而是通过 3344 服务间接性的连接到!
注意: 这个文件虽然名称不同,但是也是放在 resources 文件夹下的!
编写启动类
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class, args);
}
}
编写业务类
@RestController
@Slf4j
public class ConfigClinetController {
@Value("${config.info}")
private String configInfo;
@GetMapping(value = "/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
测试
启动顺序:7001–3344–3355
我们先来看一下 Eureka 中注册进来的服务
可以发现,3344 和 3355 都已经在注册中心里了!
然后我们在来看一下 3344 是否能够正常执行
http://config-3344.com:3344/master/config-dev.yaml
最后我们再来测试 3355 的效果
我们可以发现,3344 读取到的内容,在 3355 中也可以正常的获取到
以上完成后,虽然 3355 中已经可以通过 3344 获取到了 git 仓库中某个环境的信息,但是如果这个时候我们更新了 git 仓库中的某一个环境版本,那么还能获取到吗?接下来我们就来看一下
我们先把 git 仓库中的 dev 环境版本改变为 2
然后在使用刚才测试的两个路径进行访问
http://config-3344.com:3344/master/config-dev.yaml
http://localhost:3355/configInfo
我们可以发现,3344 执行的时候可以直接获取到仓库中最新的数据,而3355 的话就无法获取到最新的数据!除非自己重启或重新加载,这样非常的麻烦,所以我们接下来就来解决这个动态刷新的问题。
Config动态刷新(手动版)
避免每次更新配置都要重启客户端微服务(3355)
先启动服务:7001–3344-3355
实现步骤:
-
在 3355 服务的pom文件中引入 actuator 监控
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
我们在之前已经导入了,这里大家了解一下即可
-
修改 3355服务中的 yml 文件,暴露监控端口
server: port: 3355 spring: application: name: config-client cloud: # Config 客户端配置 config: # 分支名称 label: master # 配置文件名称 name: config # 读取后缀名称 profile: dev # 配置中心地址 uri: http://localhost:3344 # 服务注册到 Eureka 地址 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka # 暴露监控端点 management: endpoints: web: exposure: include: "*"
-
为 3355 的 controller 类加上 @RefreshScope 注解
/** * @RefreshScope:帮助我们做局部的参数刷新 */ @RestController @Slf4j @RefreshScope public class ConfigClinetController { @Value("${config.info}") private String configInfo; @GetMapping(value = "/configInfo") public String getConfigInfo(){ return configInfo; } }
-
测试
我们在按照刚才的方式进行测试,首先修改 git 仓库中的某个数据,然后测试方法
这里我们就修改 test 环境的版本号
这里需要注意修改完后再去修改 bootstrap 文件中的指定环境,如下
server: port: 3355 spring: application: name: config-client cloud: # Config 客户端配置 config: # 分支名称 label: master # 配置文件名称 name: config # 读取后缀名称 # 上述3个综合:master 分支上 config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yaml profile: test # 配置中心地址 uri: http://localhost:3344 # 服务注册到 Eureka 地址 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka # 暴露监控端点 management: endpoints: web: exposure: include: "*"
不进行修改的话就无法正确的查看对应信息!
http://config-3344.com:3344/master/config-dev.yaml
http://localhost:3355/configInfo
这次我们就可以发现,似乎配置了这些东西之后 3355 还是不能解决之前出现的问题,还是需要重启才能获取到最新的数据,其实配置好这些东西之后我们还需要使用命令行的方式告诉它我们已经修改了数据,然后它才可以正常的获取到最新的数据!
使用 win+R 输入 cmd 打开命令行页面输入以下命令通知客户端数据已经更新
curl -X POST "http://localhost:3355/actuator/refresh"
执行完后我们再来进行测试查看效果
现在的话我们就已经可以正常获取到最新的数据啦。当然,如果以后想要获取最新的数据的话还是需要在此输入命令或者重启项目才能正常生效! 但是这种方式比重启服务要好很多。
注意: 如果在下一次改变仓库数据后直接执行的话也是不能正常获取到数据的!!!
动态版
手动版比较适合用于少量服务的使用,并不适合大量服务的时候,因为大量服务就算弄个脚本也稍微有一点麻烦!
但是如果是依靠 Config 本身想要实现动态刷新的话暂时还是做不到的,所以下一章会教大家使用 Spring Cloud Bus 的方式来进行动态刷新。