Spring Cloud Config是Spring Cloud团队创建的一个全新项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为服务端与客户端两个部分。
SpringCloudConfig实现了对服务端和客户端中环境变量和属性配置的抽象映射,所以它除了适用于Spring构建的应用程序之外,也可以在任何其他语言运行的应用程序中使用。由于Spring Cloud Config实现的配置中心默认采用Git来存储配置信息,所以使用Spring Cloud Config 构建的配置服务器,天然就支持对微服务应用配置信息的版本管理,并且可以通过Git 客户端工具来方便地管理和访问配置内容。当然它也提供了对其他存储方式的支持,比如SVN仓库、本地化文件系统。接下来,我们从一个简单的入门示例开始学习Spring Cloud Config服务端以及客户端的详细构建与使用方法。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.2version>
<relativePath/>
parent>
<groupId>com.cloudgroupId>
<artifactId>config-serverartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>config-servername>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
<version>3.0.0version>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>2020.0.2version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
server.port=7001
# 应用名称
spring.application.name=config-server
# 注册服务的时候使用服务的ip地址
eureka.instance.prefer-ip-address=false
eureka.client.service-url.defaultZone=http://peer2:1112/eureka/
# 配置git仓库信息
## 你自己创建的远程仓库
spring.cloud.config.server.git.uri=https://gitee.com/ninesuntec/spring-cloud-config/
## 配置仓库下的相对搜索位置
spring.cloud.config.server.git.search-paths=spring_cloud_in_action/config-repo
## 访问git仓库的用户名
spring.cloud.config.server.git.username=登录git的邮箱
## 访问仓库的密码
spring.cloud.config.server.git.password=登录git的密码
注意:我圈起来的部分均是需要自己创建的目录
为了验证上面完成的分布式配置中心config-server,根据Git配置信息中指定的仓库位置,在spring_ cloud_ in_ action/config-repo下根据不同环境新建下面4个配置文件:
并且在这4个配置文件中均设置了一个from属性,分别如下所示
为了测试版本控制,在该Git仓库的master分支中,我们为from属性加入1.0的后缀,同时创建一个config-label-test分支,并将各配置文件中的值用2.0作为后缀。
git指令如下:
git branch config-label-test
git push origin config-label-test
在config-label-test同样创建4个文件,但是内容的后缀改为2.0
from=git-default-2.0
from=git-dev-2.0
from=git-test-2.0
from=git-prod-2.0
上面的url会映射{application}-{profile} .properties对应的配置文件,其中{ label}对应Git 上不同的分支,默认为master。
我们可以尝试构造不同的url来访问不同的配置内容,比如,要访问 config-label-test 分支,ninesuntec应用的prod环境,就可以访问这个url:
http://localhost:7001/didispace/prod/config-label-test/
接下来我们尝试访问主分支上的didispace-dev.properties文件的内容
http://localhost:7001/didispace-dev.properties
我们再次尝试访问config-label-test分支上的内容
http://localhost:7001/config-label-test/didispace-dev.properties
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.4version>
<relativePath/>
parent>
<properties>
<java.version>11java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
<version>3.0.4version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bootstrapartifactId>
<version>3.0.3version>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>2020.0.2version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
spring.application.name=didispace
# 注册服务的时候使用服务的ip地址
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://peer2:1112/eureka/
#指定的环境
spring.cloud.config.profile=dev
#指定分支,当使用git的时候,默认是master
spring.cloud.config.label=master
#Config server的uri
spring.cloud.config.uri=http://localhost:7001/
# 配置项目启动端口号
server.port=7002
注意:spring.application.name要和你git仓库中的配置前缀相对应
这里需要格外注意,上面这些属性必须配置在 bootstrap.properties 中,这样config-server中的配置信息才能被正确加载
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
通过@value ("${from} ")绑定配置服务中配置的from属性,具体实现如下:
//@RefreshScope是为了可以动态刷新这个Controller的Bean
@RefreshScope
@RestController
public class TestController {
@Value("${from}")
private String from;
@RequestMapping(value = "from", method = RequestMethod.GET)
public String from() {
return this.from;
}
}
重启项目访问http://localhost:7002/from
除@Value之外,我们还可以通过Environment对象来获取配置属性,比如:
//@RefreshScope是为了可以动态刷新这个Controller的Bean
@RefreshScope
@RestController
public class TestEnviromentController {
@Autowired
private Environment env;
@RequestMapping(value = "from-env", method = RequestMethod.GET)
public String from() {
return env.getProperty("from", "undefined");
}
}
接下来我们深入理解一下这个架构是怎么运作起来的,主要包含以下几个要素:
客户端应用从配置管理中获取配置信息遵从下面的执行流程:
Config Server巧妙地通过git clone 将配置信息存于本地,起到了缓存的作用,即使当Git服务端无法访问的时候,依然可以取 Config Server 中的缓存内容进行使用。
由于配置中心存储的内容比较敏感,做一定的安全处理是必需的。为配置中心实现安全保护的方式有很多,比如物理网络限制、OAuth2授权等。不过,由于我们的微服务应用和配置中心都构建于Spring Boot基础上,所以与Spring Security结合使用会更加方便。
我们只需要在config-server配置中心的pom.xml中加入spring-boot-starter-security 依赖,不需要做任何其他改动就能实现对配置中心访问的安全保护。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
<version>2.5.4version>
dependency>
当我们重启项目之后,会给出一个默认的随机生成的密码,但是我们在实际开发中 一般会自己去定义一个用户名和密码
# 设置访问配置的用户名和密码
spring.security.user.name=root
spring.security.user.password=123456
由于我们设置了用户名和密码,所以在client进行连接时如果没有设置响应的安全信息,那么获取配置就会返回401错误
解决方式如下:在config-client的配置中添加:
# 设置安全访问的用户名和密码
spring.cloud.config.username=root
spring.cloud.config.password=123456
当要将配置中心部署到生产环境中时,与服务注册中心一样,我们也希望它是一个高可用的应用。
Spring Cloud Config实现服务端的高可用非常简单,主要有以下两种方式:
传统模式:不需要为这些服务端做任何额外的配置,只需要遵守一个配置规则,将所有的Config Server都指向同一个Git仓库,这样所有的配置内容就通过统一的共享文件系统来维护。而客户端在指定Config Server位置时,只需要配置Config Server上层的负载均衡设备地址即可,就如下图所示的结构。
服务模式:除了上面这种传统的实现模式之外,我们也可以将Config Server作为一个普通的微服务应用,纳入 Eureka 的服务治理体系中。这样我们的微服务应用就可以通过配置中心的服务名来获取配置信息,这种方式比起传统的实现模式来说更加有利于维护,因为对于服务端的负载均衡配置和客户端的配置中心指定都通过服务治理机制一并解决了,既实现了高可用,也实现了自维护。
由于这部分的实现需要客户端的配合,具体示例我将会在下面的“客户端详解”一节中的“服务化配置中心”小节中进行展示。
在本节中我们将config-server注册到服务中心,并且通过服务发现来访问config-server并获取git仓库中的配置信息。
在config-server的pom文件中添加以下依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.7.RELEASEversion>
dependency>
修改application.properties文件,添加以下配置
# 注册服务的时候使用服务的ip地址
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:1112/eureka/
这些配置在刚开始入门的时候我们已经添加过所以无需添加
修改主配置类
@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
至此config-server的配置就结束了
在config-client的pom文件中新增以下依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.7.RELEASEversion>
dependency>
在bootstrap.properties文件中添加以下配置:
spring.application.name=didispace
# 注册服务的时候使用服务的ip地址
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:1112/eureka/
# 开启通过服务来访问config server功能
spring.cloud.config.discovery.enabled=true
# 通过serviceId来指定config server注册的服务名
spring.cloud.config.discovery.service-id=config-server
#指定的环境
spring.cloud.config.profile=dev
#指定分支,当使用git的时候,默认是master
spring.cloud.config.label=master
###Config server的uri
#spring.cloud.config.uri=http://localhost:7001/
# 配置项目启动端口号
server.port=7002
# 设置安全访问的用户名和密码
spring.cloud.config.username=root
spring.cloud.config.password=123456
此处我已经将之前的通过config.uri来访问配置给注释掉了
修改主配置类
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
再次测试,访问http://localhost:7002/from-env 可以发现还是可以拿到数据
Spring Cloud Config 的客户端会预先加载很多其他信息,然后再开始连接Config Server进行属性的注入。
当我们构建的应用较为复杂的时候,可能在连接Config Server之前花费较长的启动时间,而在一些特殊场景下,我们又希望可以快速知道当前应用是否能顺利地从Config Server 获取到配置信息,这对在初期构建调试环境时,可以减少很多等待启动的时间。要实现客户端优先判断Config Server 获取是否正常,并快速响应失败内容,只需在bootstrap.properties 中配置参数spring.cloud.config.failFast= true即可。
在未加该配置之前,如果我们连接config-server失败,那么就会提示以下信息:
虽然仍然启动失败,但是前置的加载内容少了很多,这样通过该参数有效避免了当Config Server配置有误时,不需要多等待前置的一些加载时间,实现了快速返回失败信息。
上面,我们演示了当Config Server宕机或是客户端配置不正确导致连接不到而启动失败的情况,快速响应的配置可以发挥比较好的效果。但是,若只是因为网络波动等其他间歇性原因导致的问题,直接启动失败似乎代价有些高。所以,Config 客户端还提供了自动重试的功能,在开启重试功能前,先确保已经配置了spring.cloud.config.failFast=true,再进行下面的操作。
<dependency>
<groupId>org.springframework.retrygroupId>
<artifactId>spring-retryartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
<version>2.5.4version>
dependency>
若对默认的最大重试次数和重试间隔等设置不满意,还可以通过下面的参数进行调整。
#初始重试间隔时间(单位为毫秒),默认为1000毫秒。
spring.cloud.config.retry.multiplier=1000
# 下一间隔的乘数,默认为1.1,所以当最初间隔是1000毫秒时,下一次失败后的间隔为1100毫秒。
spring.cloud.config.retry.initial-interval=1.1
# 最大间隔时间,默认为2000毫秒。
spring.cloud.config.retry.max-interval=2000
# 最大重试次数,默认为6次。
spring.cloud.config.retry.max-attempts=6
有时候,我们需要对配置内容做一些实时更新,那么Spring Cloud Config是否可以实现呢?
答案显然是可以的。
下面,我们以快速入门中的示例作为基础,看看如何进行改造来实现配置内容的实时更新。
首先,回顾一下,当前我们已经实现了哪些内容。
在改造程序之前,我们先将config-server和 config-client都启动起来,并访问客户端提供的接口http://localhost:7002/from来获取配置信息,获得的返回内容为git-dev-1.0。
接着,我们可以尝试使用Git工具修改当前配置的内容,比如,将config-repo/didispace-dev.properties 中的 from 的值从from=git-dev-1.0修改为from=git-dev-1.0-modify,再访问http://localhost:7002/from,可以看到其返回内容还是git-dev-1.0。
接下来,我们将在config-client端做一些改造以实现配置信息的动态刷新。在config-client的pom.xml中新增spring-boot-starter-actuator监控模块。其中包含了/refresh端点的实现,该端点将用于实现客户端应用配置信息的重新获取与刷新。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
<version>2.5.4version>
dependency>
修改bootstrap.properties文件,添加以下配置:
#actuator配置
management.endpoints.enabled-by-default=true
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=*
management.endpoints.web.base-path=/actuator
重新启动config-client,访问一次http://localhost:7002/from,可以看到当前的配置值已经变成我们修改之后的值。
我们再次修改Git仓库config-repo/didispace-dev.properties文件中from的值。再访问一次http://localhost:7002/from,可以看到配置值没有改变。
通过POST请求发送到http://localhost:7002/actuator/refresh,我们可以看到返回内容如下,代表from参数的配置内容被更新了;
但是,当我们的系统发展壮大之后,维护这样的刷新清单也将成为一个非常大的负担,而且很容易犯错,那么有什么办法可以解决这个复杂度呢?后续我们将介绍如何通过Spring Cloud Bus 来实现以消息总线的方式进行配置变更的通知,并完成集群上的批量配置更新。