03.Springcloud Config Server

一、分布式Config

1.分布式配置架构

传统架构

1574906139708

可以看到,在传统架构中,每个配置硬编码到应用中,写在properties或者xml里,跟随这项目一起保存到git或者svn仓库中。当配置越来越多的时候,比如MQ,数据库,Redis,ES等等的配置,只能一个文件一个文件的累加,并且每当修改时都需要重新打包部署,浪费时间且冗余。

缺点:

  • 硬编码
  • 写在properties,集群环境下需要替换和重启
  • 写在xml中,和应用一起打包,替换需要重新打包部署

SpringCloud Config

1574907736335

由于常用的项目+配置的方式有很大缺点,SpringCloud提供了一个配置中心来统一管理配置,它分为服务端和客户端。解决了配置中心化,版本控制,硬编码等问题

服务端即分布式配置中心,为一个单独的微服务应用,用于连接客户端与仓库,为客户端提供相应的配置信息等。服务器默认采用git来存储配置信息,这样有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。

客户端是指通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。

2.EnvironmentRepository仓储

这里我们先介绍一下SpringCloudConfig的仓储,对于配置服务器管理多个配置,客户端获取时需要一定的规则来获取,SpringCloudConfigServer提供了EnvironmentRepository接口供客户端获取,其具体规则为:

  • {application}:配置客户端应用名称
  • {profile}:客户端使用的配置文件环境,比如开发环境或线上环境
  • {label}:版本信息,比如git的分支master or dev

服务端配置映射主要有五种:

  • /{application}/{profile}[/{label}]
  • /{application}-{profile}.yml
  • /{label}/{application}-{profile}.yml
  • /{application}-{profile}.properties
  • /{label}/{application}-{profile}.properties

3.搭建Spring Cloud Conifg Server

关于SpringCloud Config Server的搭建非常简单,最主要是用到@EnableConfigServer这个注解

首先需要基于SpringBoot创建一个SpringCloud应用,pom文件额外需要添加:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

基于本地Git仓库

1.需要在启动器类上加入@EnableConfigServer注解

2.在application.properties中添加配置信息,如下:主要是配置本地git路径

1
2
3
4
5
6
7
8
9
10
11
12
## 配置服务器应用名称
spring.application.name = spring-cloud-config-server

## 配置服务器端口
server.port = 9090

## 关闭管理端actuator 的安全
## /env /health 端口完全开放
management.security.enabled = false

# 配置本地git路径
spring.cloud.config.server.git.uri=${user.dir}/src/main/resources/configs

Tips:${user.dir}为此项目在电脑上的绝对路径,比如windows系统下(D:\ideaProject\SpringCloud...),如果在linux系统下即(home/…),使用这个做路径可以很好的适配各个平台的文件路径

3.对应本地git路径创建文件夹及文件

1574925877737

这里我们创建三个配置文件

  • enbuys:默认的配置
    • name=enbuys
  • enbuys-dev:开发环境时的配置
    • name=enbuys-dev
  • enbuys-prod:线上生产环境配置
    • name=enbuys-prod

4.在configs目录下进行git初始化

1
2
3
> git init
> git add .
> git commit -m "first commit"

5.启动测试

访问http://localhost:9090/enbuys/test

1574926580867

因为我想访问dev的,打错了,所以可以发现,如果输入一个没有的配置文件,其会自动使用默认的配置文件

再访问下http://localhost:9090/enbuys/dev

1574926665039

可以发现会把enbuys-dev与默认配置文件都输出出来

我们这里使用的映射即为/{label}/{application}-{profile}.properties

基于远程Git仓库

即把我们的配置放到github上面,方法也很简单,和本地仓库类似

1.添加注解@EnableConfigServer

2.在github上创建一个Repository

1574928539755

3.根据github提示,找个文件夹绑定远程仓库

1574928605187

4.将三个配置文件放到init的那个文件夹,上传到远程git仓库

1
2
3
> git add .
> git commit -m 'add config server'
> git push -u origin master

5.修改项目中的git.url路径

1
2
# 远程git仓库
spring.cloud.config.server.git.uri=https://github.com/PAcee1/temp

6.测试

1574928714779

可以看到正确输出远程仓库上的信息

Tips:spring.cloud.config.server.git.force-pull,因为git拉取时有时会走本地缓存,如果配置这个可以保证强制拉取远程仓库上的配置

不同方式访问

还记得我们在上面介绍EnvironmentRepository仓储时,对资源的映射方式有好几种,我们来测试一下:

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

这种就是我们上面使用的,对于请求比较模糊时使用,会输出很多信息

/{application}-{profile}.properties

1574929426681

使用这种方式,可以具体的显示出配置文件中的信息

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

1574990626389

也是一样输出详细信息

4.搭建Spring Cloud Config Client

客户端加入的pom依赖和server不同,为:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

搭建客户端获取服务器中的配置也非常简单

1.配置application.properties

1
2
3
4
5
6
7
8
9
## 配置客户端应用名称
spring.application.name = spring-cloud-config-client

## 配置客户端应用服务端口
server.port = 8080

## 关闭管理端actuator 的安全
## /env /health 端口完全开放
management.security.enabled = false

还是基本的一些配置信息

2.配置bootstrap.properties

1
2
3
4
5
6
7
8
9
10
## 配置客户端应用关联的应用
## spring.cloud.config.name 是可选的
## 如果没有配置,采用 ${spring.application.name}
spring.cloud.config.name = enbuys
## 关联 profile
spring.cloud.config.profile = prod
## 关联 label
spring.cloud.config.label = master
## 配置配置服务器URI
spring.cloud.config.uri = http://127.0.0.1:9090/

Tips:这里的cloud的config配置也可以写在application.properties,这里写在bootstrap的配置文件中主要还是一个规范问题,因为SpringCloud使用Bootstrap做上下文,对于关于SpringCloud的配置也应该写在bootstrap.properties

3.启动测试

1574932747419

首先可以在控制台看到SpringCloud加载时去请求服务器获取配置信息

然后在在浏览器上查看

1574932798132

可以看到也正确加载到Environment中了

5.动态配置Bean

动态配置Bean在之前学springBoot的时候已经做过,无非是在Bean上加一个@ConfigurationProperties注解

这里我们要说的是,从SpringCloud Config Server拉取Bean的信息进行注入,

客户端配置文件注入

Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@ConfigurationProperties(prefix = "enbuy.user")
public class User {

private Integer id;

private String name;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}

application.properties:

1
2
3
#配置user信息
enbuy.user.id=1
enbuy.user.name=pacee

UserController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@EnableConfigurationProperties(User.class)
public class UserController {

//通过构造器注入
private final User user;

@Autowired
public UserController(User user) {
this.user = user;
}

@GetMapping("/user")
public User user() {
return user;
}
}

访问http://localhost:8080/user

1574997332997

正确显示出来

服务器配置文件注入

其次,我们将配置放到github上的配置文件上,然后再进行拉取远程配置注入

1574997494038

再次访问http://localhost:8080/user

1574997510628

可以发现还是原来的信息,这是为什么呢?

因为这里使用的还是原来的远程配置,没有动态的刷新配置,这里我们先对客户端环境进行refresh

1574997659890

可以看到,刷新了三个信息,版本以及我们修改的Bean信息。

再次请求

1574997695173

可以发现正确修改成远程配置服务器上的信息了。这里有两个问题需要注意的:!

==1.拉取远程服务器上的配置,比客户端中application或bootstrap的配置优先级高==

==2.SpringCloud当配置服务器修改时,不会自动刷新客户端拉取新的配置==

自动拉取服务器配置

这里我们就应该对第二个问题展开探究,这里实现的方式有很多种

  • SpringCloud提供的SpringCloud Bus
  • 使用定时器配合ContextRefresh进行刷新

这里我们先使用ContextRefresh,因为这种方式适配各种情景,比如我们不使用http请求即Git当配置服务器,而是zookeeper实现配置服务器,这种方式就可以完全适应,不用修改任何东西。

Java代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootApplication
public class SpringCloudLesson3ConfigClientApplication {

@Autowired
private ContextRefresher contextRefresher;

public static void main(String[] args) {
SpringApplication.run(SpringCloudLesson3ConfigClientApplication.class, args);
}

@Scheduled(fixedRate = 1000L)
public void update() {

Set<String> keys = contextRefresher.refresh();

if (!keys.isEmpty()) {
System.out.println("本次更新的配置项: " + keys);
}

}
}

可以看到,这里我们使用1秒刷新一次的策略,代码也是比较简单。

1574998138847

可以看到,正确刷新了我们服务器上修改的配置

1574998152403

请求user,输出信息也是我们期望的。

这样我们就简单实现了自动拉取配置服务器上修改的内容了

二、Health EndPoint

之前介绍过几个传动器端点,前面一直在使用env这个端点,现在说下health端点。

这个端点可以查看服务的健康程度,就包含我们刚刚配置的Config Server

因为我们上面已经配了全局的端点敏感性为false,所以可以直接查看

1575013130538

可以看到默认包含四个PropertySource

  • status:总体健康程度
  • diskSpace:磁盘健康
  • refreshScope:刷新的健康
  • configServer:配置服务器的健康程度

自定义健康组件

我们先观察下Health的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@ConfigurationProperties(prefix = "endpoints.health")
public class HealthEndpoint extends AbstractEndpoint<Health> {

private final HealthIndicator healthIndicator;

/**
* Time to live for cached result, in milliseconds.
*/
private long timeToLive = 1000;

/**
* Create a new {@link HealthEndpoint} instance.
* @param healthAggregator the health aggregator
* @param healthIndicators the health indicators
*/
public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
super("health", false);
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
healthAggregator);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(getKey(entry.getKey()), entry.getValue());
}
this.healthIndicator = healthIndicator;
}

可以发现,在构造函数中,HealthEndpoint循环HealthIndicator加入到容器中,看到这个就想起在SpringBoot学习时,那些自动配置类也是这样循环加入容器的,我们便可以仿照SpringBoot添加组件的方式,创建一个类实现HealthIndicator,然后添加到容器中。

1.创建一个类实现`HealthIndicator`

1
2
3
4
5
6
7
public class HealthCustomer implements HealthIndicator {

@Override
public Health health() {
return null;
}
}

可以看到这里需要返回一个Health对象,再进到Health里查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class Health {

private final Status status;

private final Map<String, Object> details;

/**
* Create a new {@link Health} instance with the specified status and details.
* @param builder the Builder to use
*/
private Health(Builder builder) {
Assert.notNull(builder, "Builder must not be null");
this.status = builder.status;
this.details = Collections.unmodifiableMap(builder.details);
}

可以看到Health是由Builder创建的,那意思是我们也需要创建一个Builder来初始化Health,再看下Builder

1
2
3
4
5
6
7
8
9
10
11
12
13
public static class Builder {

private Status status;

private Map<String, Object> details;

/**
* Create new Builder instance.
*/
public Builder() {
this.status = Status.UNKNOWN;
this.details = new LinkedHashMap<String, Object>();
}

Builder的创建就很简单了,传递status状态已经具体的detailsMap信息即可

2.在我们的类中创建Builder并初始化Health返回

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class HealthCustomer implements HealthIndicator {

@Override
public Health health() {
Health.Builder builder = new Health.Builder();
builder.status(Status.UP);
builder.withDetail("name","MyHealthCustomer");
builder.withDetail("time",System.currentTimeMillis());
Health health = builder.build();
return health;
}
}

这样就创建完毕我们自定义的Health组件了,记得要加上@Component注解

3.浏览器查看

1575013792987

正确加载进HealthEndpoint