前言
在日常开发中,我们不难发现,一个应用中不只是代码,还需要连接资源和其它应用,经常有很多需要外部设置的项去调整应用行为,如切换不同的数据库,设置功能开关等。
随着系统微服务的不断增加,首要考虑的是系统的可伸缩、可扩展性好,随之就是一个配置管理的问题。各自管各自的开发时没什么问题,到了线上之后管理就会很头疼,配置环境越来越多,测试环境、开发环境、生产环境、发布环境等等,到了要大规模更新就更麻烦了。
而且我们不可能停止我们的服务集群去更新配置,这是不现实的做法,因此Spring Cloud配置中心就是一个比较好的解决方案,下图就是一个Spring Cloud配置中心的解决方案:
其架构图如下:
Spring Cloud Config介绍
在SpringCloud中,Spring Cloud Config为分布式系统中的外部化配置提供服务器和客户端支持。使用Config Server,我们可以集中管理所有环境中应用程序的外部属性。客户机和服务器上的概念与Spring环境和PropertySource抽象完全相同,因此它们非常适合Spring应用程序,但可以与任何语言运行的应用程序一起使用。随着应用程序在部署管道中从开发到测试再到生产,我们可以管理这些环境之间的配置,并确保应用程序在迁移时具备运行所需的一切。简而言之就是通过Spring Cloud Config可以让我们可以把配置放到远程服务器,集中化管理集群配置。服务器存储后端的默认实现使用git,因此它可以轻松地支持标记版本的配置环境,并且可以使用各种工具来管理内容。很容易添加替代实现并将其插入Spring配置。
Spring Cloud Config与常见配置中心的对比
常见的配置中心的实现方法有:
硬编码(缺点:需要修改代码,风险大)
放在xml等配置文件中,和应用一起打包(缺点:需要重新打包和重启)
文件系统中(缺点:依赖操作系统等)
环境变量(缺点:有大量的配置需要人工设置到环境变量中,不便于管理且依赖平台)
云端存储(缺点:与其他应用耦合)
Spring Cloud Config就是云端存储配置信息的,它具有中心化、版本控制、支持动态更新、平台独立、语言独立等特性。其特点是:
提供服务端和客户端支持(spring cloud config server和spring cloud config client)
集中式管理分布式环境下的应用配置
基于Spring环境,无缝与Spring应用集成
可用于任何语言开发的程序
默认实现基于git仓库,可以进行版本管理
可替换自定义实现
Spring Cloud Config服务端与客户端功能对比
Spring Cloud Config Server功能:
HTTP,用于外部配置的基于资源的API(名称-值对或等效的YAML内容)
加密和解密属性值(对称或非对称)
可使用@EnableConfigServer轻松嵌入Spring Boot应用程序
拉取配置时更新git仓库副本,保证是最新结果
支持数据结构丰富,yml、json、properties 等
配合 其他注册中心可实现服务发现,配合Spring Cloud Bus 可实现配置推送更新
配置存储基于 git仓库,可进行版本管理
简单可靠,有丰富的配套方案
Spring Cloud Config Client 功能(适用于Spring应用程序):
绑定到Config Server并使用远程属性源初始化Spring环境
加密和解密属性值(对称或非对称)
更多详情介绍可参考Spring Cloud Config参考文档
Spring Cloud Bus介绍
Spring Cloud Bus将分布式系统的节点与轻量级消息代理链接。这可以用于广播状态更改(例如配置更改)或其他管理指令。一个关键的想法是,总线就像一个分布式执行器,用于扩展的Spring Boot应用程序,但也可以用作应用程序之间的通信通道。目前唯一的实现是使用AMQP代理作为传输,但是相同的基本功能集(还有一些取决于传输)在其他传输的路线图上。
Spring Cloud Bus的工作原理是添加Spring Boot自动配置,如果它在类路径中检测到自身。所有您需要做的是启用总线是将spring-cloud-starter-bus-amqp或spring-cloud-starter-bus-kafka添加到您的依赖关系管理中。
消息总线当前支持向所有节点发送消息,用于特定服务的所有节点。通过与Spring Cloud Config搭配使用,能够实现对配置文件的热部署实现,动态刷新客户端获取Config配置中心的配置文件。
更多详情介绍可参考Spring Cloud Bus参考文档
Spring Cloud Config Server配置及使用
我们应该在哪里向配置服务器存储配置数据呢?在Spring Cloud Config中主要通过EnvironmentRepository,它为环境对象提供服务。此环境是Spring环境中域的一个浅拷贝(包括propertySource作为主要特性)。环境资源由三个变量参数化:
{application},它映射到客户端的spring.application.name。
{profile},它映射到客户端上的spring.profile.active(逗号分隔的列表)。
{label},这是一个服务器端功能,标记了一组“版本化”的配置文件。
存储库实现的行为通常类似于Spring Boot应用程序,从等于{application}参数的Spring.config.name加载配置文件,而Spring.profile.active等于{profiles}参数。配置文件的优先级规则也与常规Spring Boot应用程序中的相同: Active profiles文件优先于默认配置文件,如果有多个配置文件,最后一个配置文件优先被读取。
spring:
application:
name: foo
profiles:
active: dev,mysql
(与Spring Boot应用程序一样,这些属性也可以由环境变量或命令行参数设置)。
如果存储库是基于文件的,服务器将从application.yml(在所有客户端之间共享)和foo.yml创建一个环境(foo.yml优先)。
如果YAML文件中包含指向Spring配置文件的文档,则这些文档将以更高的优先级应用(按照列出的配置文件的顺序)。
如果存在特定于概要文件的YAML(或properties)文件,则应用这些文件的优先级也高于默认值。更高的优先级将转换为先前在环境中列出的PropertySource。(这些规则适用于独立的Spring Boot应用程序。)
我们可以将spring.cloud.config.server.accept-empty设置为false,以便如果找不到应用程序,服务器将返回HTTP 404状态。默认情况下,此标志设置为true。
EnvironmentRepository的默认实现使用Git后端,这对于管理升级和物理环境以及审核更改非常方便。要更改存储库的位置,可以在config server中设置spring.cloud.config.server.git.uri配置属性(例如在application.yml中)。如果使用file:前缀进行设置,它也可以在本地存储库中工作,这样就可以在没有服务器的情况下快速轻松地启动。但是,在这种情况下,服务器直接在本地存储库上运行,而无需克隆它(如果它不是裸机也无所谓,因为Config server从不更改“远程”存储库)。要扩展配置服务器并使其高度可用,需要使服务器的所有实例指向同一存储库,因此只有共享文件系统才能工作。即使在这种情况下,最好将ssh:协议用于共享文件系统存储库,以便服务器可以克隆它并将本地工作副本用作缓存。
定位资源的默认策略是克隆一个git仓库(在spring.cloud.config.server.git.uri),并使用它来初始化一个迷你SpringApplication。迷你应用程序的Environment用于枚举属性源并通过JSON端点发布。
HTTP服务具有以下格式的资源:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
其中“应用程序”作为SpringApplication中的spring.config.name注入(即常规Spring Boot应用程序中通常为“应用程序”),“配置文件”是活动配置文件(或逗号分隔列表)的属性),“label”是可选的git标签(默认为“master”)。
Spring Cloud Config服务器从git存储库中提取远程客户端的配置(必须提供):
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
因此,我们配置Spring Cloud Config配置中的大致步骤为以下几步:
1,创建微服务模块并导入Config Server依赖
新建一个微服务模块后,导入Config Server依赖spring-cloud-config-server,有需要也可以导入spring-boot-starter-actuator依赖来监控和管理Config Server在微服务模块中的状态。另外为了实现配置文件在git仓库中改变时的热部署,动态的实现配置修改与刷新,还需要引入Sring Cloud Bus消息总线依赖,并通过Rabbitmq来实现将配置文件修改结果以消息广播的方式分发到其他微服务节点中。大体依赖如下:
<?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>SpringCloudAlibaba</artifactId>
<groupId>com.yy</groupId>
<version>1.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>springcloud-config连接git实现远程读取配置信息,8006这个作为服务配置中心</description>
<artifactId>cloud-configcenter-8006</artifactId>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- 添加消息总线集成rabbitmq的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
2,创建git仓库并在yml文件中配置git
由于Config Server默认支持git存放配置文件的,因此首先我们需要在gitee或者gitHub上注册账号后并新建一个仓库用于存放配置文件。
这里笔者为了方便以码云为例,创建好仓库后,配备了两个配置文件,作为配置中心的配置文件。
然后在新建的配置中心模块下的yml文件中配置git信息,以及相关的服务监控,服务注册信息。
server:
port: 8006
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: https://gitee.com/yao-shen/springcloud-config.git
#搜索目录
search-paths:
- springcloud-config
default-label: master
#rabbitmq配置
rabbitmq:
virtual-host: /
host: 192.168.98.128
port: 5672
username: admin
password: admin
#eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
#rabbitmq配置中,暴露bus刷新能配置的端点
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
如果要在远程存储库上使用HTTP基本身份验证,请分别添加“username”和“password”属性(不在URL中),也就是我们登录gitee或gitHub上的账号密码。例如
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
username: trolley
password: strongpassword
3,启动类上添加注解标识Config Server
创建Config Server服务启动类并通过@EnableConfigServer注解标识配置中心服务。
package com.yy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* Author young
* Date 2022/12/27 18:12
* Description: 设置消息总线后,只需刷新config中心的bus端点即可实现一处刷新多处客户端信息更改 curl -X POST "http://localhost:8006/actuator/busrefresh"
*/
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigCenterApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterApplication.class,args);
}
}
然后我们就可以创建客户端来测试读取配置中心中的配置文件信息了。
Spring Cloud Config Client配置及使用
和上述步骤大致,这里我们先创建两个单独的微服务模块分别为8007和8008,并且导入依赖。
<?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>SpringCloudAlibaba</artifactId>
<groupId>com.yy</groupId>
<version>1.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>springcloud-config客户端,通过读取8006的服务端来获取相应的配置信息</description>
<artifactId>cloud-config-client-8007</artifactId>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 引入bootstrap读取依赖,不加出现错误
Add a spring.config.import=configserver: property to your configuration.
If configuration is not required add spring.config.import=optional:configserver: instead.
To disable this check, set spring.cloud.config.enabled=false or
spring.cloud.config.import-check.enabled=false
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- 必须引入web依赖-->
<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.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- 添加消息总线集成rabbitmq的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
配置客户端启动时,它将通过配置服务器(通过引导配置属性spring.cloud.config.uri)绑定,并使用远程属性源初始化Spring Environment。
这样做的最终结果是所有想要使用Config Server的客户端应用程序需要bootstrap.yml(或环境变量),服务器地址位于spring.cloud.config.uri。因此这里的配置文件并不是传统的application.yml文件,而是bootstrap.yml,并在这个文件中配置读取的配置文件环境,uri(对应配置中心的uri地址)以及常用的服务注册,以及节点在Actuator中的暴露配置。如下:
server:
port: 8007
spring:
application:
name: config-center-client
cloud:
#客户端配置
config:
label: master #分支名称
name: application #配置文件名称
profile: test #读取测试环境下的文件后缀名称
uri: http://localhost:8006 #配置中心地址
#rabbitmq配置
rabbitmq:
virtual-host: /
host: 192.168.98.128
port: 5672
username: admin
password: admin
#eureka服务地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
#暴露监控端点,便于服务端去刷新配置信息
management:
endpoints:
web:
exposure:
include: "*"
8008与8007的配置也大致类似。
server:
port: 8008
spring:
application:
name: config-center-client-8008
cloud:
#客户端配置
config:
label: master #分支名称
name: application #配置文件名称
profile: dev #读取生产环境下的文件后缀名称
uri: http://localhost:8006 #配置中心地址
#rabbitmq配置
rabbitmq:
virtual-host: /
host: 192.168.98.128
port: 5672
username: admin
password: admin
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
#暴露监控端点,便于服务端去刷新配置信息
management:
endpoints:
web:
exposure:
include: "*"
最后定义启动类以及相应的读取控制器即可。
8007的客户端启动类ConfigClientApplication:
/**
* Author young
* Date 2022/12/27 18:42
* Description: SpringCloudAlibaba
*/
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
}
测试读取配置文件的控制器类TestController:
package com.yy.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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Author young
* Date 2022/12/27 18:52
* Description: 测试客户端从配置中心远程git上读取配置文件信息
*/
@RestController
@RequestMapping("read")
//客户端及时刷新,另外还需要服务端主动发起对客户端的post刷新请求 curl -X POST "http://localhost:8006/actuator/refresh"
@RefreshScope
public class TestController {
@Value("${config.info}")
private String configInfo;
@GetMapping("info")
public String getMsg(){
return configInfo;
}
}
需要注意的是,这里需要使用@RefreshScope 注解来实现客户端随服务端配置文件的更改而刷新的效果。但是比较麻烦的是,即使通过Bus消息总线的配置,配置文件的更改会广播到所有客户端上,但是仍然需要Config Server服务端手动刷新服务curl -X POST "http://localhost:8006/actuator/refresh",否则即使服务端发生修改,如果没有手动刷新,客户端一样读取不到刚改后的配置文件。
测试
按顺序启动Eureka服务注册中心、服务配置中心Config Server、Config客户端8007、Config客户端8008。
启动成功后由于8007,8008可能被占用了,端口号改为了8091,8077,但是好在配置中心没问题,并不影响配置信息读取。
测试端口效果:
配置中心配置的文件信息读取成功了!
因为8007->8091配置的是测试环境下的配置文件,当我们手动修改配置中心application-test.yml文件后:
然后刷新http://localhost:8091/read/info后会发现读取更改后的配置文件并没有反应,这就需要我们手动刷新配置中心节点的监控服务了!
C:\Users\young>curl -X POST "http://localhost:8091/actuator/busrefresh"
再次刷新8091后读取配置中心的配置信息后info如下:
可以看到,更改后的配置刷新成功,并且被客户端成功读取到!