配置中心 Config
概述
分布式系统面临的配置问题:微服务意味着需要将单体应用中的业务拆分成为一个个的子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。每个服务都需要必要的配置信息才能运行。所以一套集中式的、动态的配置管理设施是需要的。SpringCloud提供了ConfigServer来解决这个问题。
Spring Cloud Config为微服务架构中的服务提供了集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
Spring Cloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器,并为客户端提供获取配置信息,加密/解密信息等访问接口。
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息来配置服务器,默认采用git来存储配置信息,这样有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
Spring Cloud Config 主要功能
1、集中管理配置文件
2、不同环境不同配置,动态化的配置更新,分环境部署例如dev/debug/test/prod
3、运行期间动态调整配置,不再需要在每个服务器部署的机器上编写配置文件,服务会从配置中心统一获取并配置自己的信息
4、当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置。
5、可以将配置信息以REST接口的形式暴露
官网详细信息介绍https://docs.spring.io/spring-cloud-config/docs/2.2.5.RELEASE/reference/html/#_spring_cloud_config_server
Config服务端配置
新建cloud-config-center-3344模块,这个模块作为服务端直接连接github,有条件可以替换成为自己的私有git仓库,在git仓库中新建一个项目,用于存放配置文件。顺便也把这个项目提交到了github上面https://github.com/JinTaoZh/lear-cloud-api
修改pom.xml文件,添加依赖
<?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>zjt-cloud-api</artifactId>
<groupId>com.zjt.cloud-api</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>
<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.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>
application.xml
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
#GitHub上面的git仓库名字 如果使用的是ssh连接 可以使用 git@github.com:JinTaoZh/zjt-cloud.git
uri: https://github.com/JinTaoZh/zjt-cloud.git
#### 搜索的目录 这个项目必须要是公开的public的,私有的可能会报404
search-paths:
- zjt-cloud
####读取分支
label: master
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
主启动类
package com.zjt.cloud;
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;
import java.util.TimeZone;
/**
* @author zjt
* @date 2020-09-15
*/
@EnableConfigServer
@EnableEurekaClient
@SpringBootApplication
public class ConfigCenter3344Application {
public static void main(String[] args) {
// 时区设置
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
SpringApplication.run(ConfigCenter3344Application.class, args);
}
}
测试 成功读取到配置文件
spring 官网给出的读取方式有一下几种 label是指读取哪个分支,application是项目名profile是 配置文件环境,最好和官方推荐命名方式保持一致
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
使用 /{application}/{profile}[/{label}]
的方式可以读取JSON格式返回值
Config客户端配置
先了解一下bootstrap.yml。 application.yml是用户级的资源配置项,bootstrap.yml是系统级别的,优先级更高。SpringCloud 会创建一个Bootstrap Context 作为Spring应用的 Application Context 的父环境配置,初始化的时候 Bootstrap Context负责总外部源加载配置属性并解析配置。这两个环境配置共享一个从外部获取的 Environment。Bootstrap 属性有高优先级,默认情况下,他们不会被本地配置覆盖,Bootstrap Context 和Application Context 有着不同的约定,所以可以新增一个 bootstrap.yml 文件来保证Bootstrap Context 和Application Context 配置的分离。 需要将Client 模块下新建一个 bootstrap.yml,这是非常关键的。bootstrap.yml加载优先级高于application.yml
新建cloud-config-client-3355
config客户端模块
修改pom.xml
<?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>zjt-cloud-api</artifactId>
<groupId>com.zjt.cloud-api</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>
<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.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
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
spring:
application:
name: cloud-config-client
cloud:
config: # config 客户端配置
label: master #分支名称
name: config # 配置文件名称
profile: dev # 读取名称后缀 结合上面的我们需要读取的是 master分支上的config-dev.yml的配置文件
uri: http://localhost:3344 # 配置中心地址 读取http://localhost:3344/master/config-dev.yml
主启动类
package com.zjt.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import java.util.TimeZone;
/**
* @author zjt
* @date 2020-09-16
*/
@EnableEurekaClient
@SpringBootApplication
public class ConfigClient3355Application {
public static void main(String[] args) {
// 时区设置
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
SpringApplication.run(ConfigClient3355Application.class, args);
}
}
业务类
package com.zjt.cloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zjt
* @date 2020-09-16
*/
@RestController
@RequestMapping("/config-client")
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@Value("${config.name}")
private String name;
@GetMapping
private Map<String, String> getConfigInfo() {
return new LinkedHashMap<String, String>() {{
put("info", configInfo);
put("name", name);
}};
}
}
在启动3355之前 修改一下github上的配置
测试
服务端成功读取
客户端成功读取
此时我们修改github上的配置文件
服务端同步成功
客户端没有同步
客户端3355只有重启之后才能读取到最新的配置,这样没有达到我们的目的
Config客户端手动刷新
避免每次更新配置后,config客户端需要重新启动才能读取最新配置的问题
修改bootstrap.yml 添加暴露监控端点
server:
port: 3355
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
spring:
application:
name: cloud-config-client
cloud:
config: # config 客户端配置
label: master #分支名称
name: config # 配置文件名称
profile: dev # 读取名称后缀 结合上面的我们需要读取的是 master分支上的config-dev.yml的配置文件
uri: http://localhost:3344 # 配置中心地址 读取http://localhost:3344/master/config-dev.yml
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
改造业务类,添加@RefreshScope
注解,这里没有直接添加到controller,发现直接添加到controller之后,返回值是null ,查找资料后发现有两种解决方案:第一种https://stackoverflow.com/questions/40221063/spring-refreshscope-is-not-working-with-component,第一种修改注解为@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
可以获取,但是尝试刷新时失败了,无法获取最新属性值,第二种抽出这些需要@Value
的属性。改造成为
package com.zjt.cloud.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* @author zjt
* @date 2020-09-16
*/
@Data
@Component
@RefreshScope
public class Config {
@Value("${config.info}")
private String configInfo;
@Value("${config.name}")
private String name;
}
package com.zjt.cloud.controller;
import com.zjt.cloud.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zjt
* @date 2020-09-16
*/
@RestController
@RequestMapping("/config-client")
public class ConfigClientController {
@Autowired
private Config config;
@GetMapping
private Map<String, String> getConfigInfo() {
return new LinkedHashMap<String, String>() {{
put("info", config.getConfigInfo());
put("name", config.getName());
}};
}
}
测试,最新值为
此时3355请求依旧是之前的
使用curl 命令通知3355配置已经更新http://localhost:3355/actuator/refresh
再次获取3355
这样可以做到手动刷新3355配置,但是依旧存在下面的问题,太麻烦,每个微服务都需要post请求一下,手动刷新,无法做到广播通知,一次通知处处生效。那么消息总线BUS就进入视线。