配置中心-Config

0
字数 3.7k
阅读时间 7 分钟

Spring Cloud Config

Config 的 Github 页面 Readme 对 Config 描述如下:

Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system. With the Config Server, you have a central place to manage external properties for applications across all environments. The concepts on both client and server map identically to the Spring Environment and PropertySource abstractions, so they fit very well with Spring applications but can be used with any application running in any language. As an application moves through the deployment pipeline from dev to test and into production, you can manage the configuration between those environments and be certain that applications have everything they need to run when they migrate. The default implementation of the server storage backend uses git, so it easily supports labelled versions of configuration environments as well as being accessible to a wide range of tooling for managing the content. It is easy to add alternative implementations and plug them in with Spring configuration.

Spring Cloud Config 为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Config Server,您可以在所有环境中管理应用程序的外部属性。客户端和服务器上的概念映射与 Spring Environment 和 PropertySource 抽象,因此它们非常适合 Spring 应用程序,但可以与任何语言运行的任何应用程序一起使用。当应用程序通过部署管道从开发到测试再到生产时,您可以管理这些环境之间的配置,并确保应用程序具有迁移时需要运行的所有内容。服务器存储后端的默认实现使用 git,因此它可以轻松支持配置环境的标签版本,以及可用于管理内容的各种工具。添加替代实现并使用 Spring 配置插入它们很容易。

简单来讲(从名字看)官方对 Config 的定位是外部化配置支持

创建 Config 项目

创建 config 项目依赖继承 config-server 项目,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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.liuzhuoming23</groupId>
<artifactId>config-center</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>

<artifactId>config</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>config</name>
<description>Demo project for Spring Cloud</description>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.yml 配置为:

server:
port: 8040

spring:
application:
name: @pom.artifactId@
cloud:
config:
server:
git:
#存放配置文件的git仓库地址
uri: https://gitee.com/liuzhuoming23/config-repo
#配置文件的存放路径(可以根据目录名存放不同项目的配置文件)
search-paths: config
#git账号密码,公共仓库不需要配置
username:
password:

因为连接速度原因,这里选择了 gitee 作为配置仓库。
ConfigApplication 启动类添加 EnableConfigServer 注解:

package com.gihub.liuzhuoming23.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

/**
* 启动类
*
* @author x-047
*/
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {

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

}

git 仓库的项目目录结构如下:

config
└─producer-client-dev.yml
└─producer-client-test.yml

其中 producer-client-dev.yml 文件内容为:

config:
name: 🍺🍐🐮🍺🐎

producer-client-test.yml 文件内容为:

config.name: 🍺🍐🍺🍐🐮🍺

启动项目,访问http://localhost:8040/producer-client/dev,返回结果:

{
"name": "producer-client",
"profiles": ["dev"],
"label": null,
"version": "0c998449b05236b778b072ec7bd28d1f09cfc5a1",
"state": null,
"propertySources": [
{
"name": "https://gitee.com/liuzhuoming23/config-repo/config/producer-client-dev.yml",
"source": {
"config.name": "🍺🍐🐮🍺🐎"
}
}
]
}

访问http://localhost:8040/producer-client/test,返回结果:

{
"name": "producer-client",
"profiles": ["test"],
"label": null,
"version": "a124d99007d7ad0c849878ee1d8e9624a77368c3",
"state": null,
"propertySources": [
{
"name": "https://gitee.com/liuzhuoming23/config-repo/config/producer-client-test.yml",
"source": {
"config.name": "🍺🍐🍺🍐🐮🍺"
}
}
]
}

说明项目配置成功。
可以看出读取哪个文件的配置是根据 url 的后面两段来匹配的,http://localhost:8040/producer-client/test匹配producer-client-testhttp://localhost:8040/producer-client/dev匹配producer-client-dev,和 spring 的 profiles 十分类似,实际上在本 doom 里面就是当成 profiles 来使用的,producer-client 为服务名,test/dev 为 profiles。

服务生产者获取外部配置

修改 config 项目的 pom.xml 文件,添加 spring-cloud-starter-netflix-eureka-client 依赖:

<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.liuzhuoming23</groupId>
<artifactId>config-center</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>

<artifactId>config</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>config</name>
<description>Demo project for Spring Cloud</description>

<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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

然后把 config 服务注册到 eureka,即修改 application.yml 为:

server:
port: 8040

spring:
application:
name: @pom.artifactId@
cloud:
config:
server:
git:
uri: https://gitee.com/liuzhuoming23/config-repo
search-paths: config
username:
password:

eureka:
client:
service-url:
defaultZone: http://admin:admin@localhost:8000/eureka/

然后在 producer-client 项目的 pom.xml 添加 spring-cloud-starter-config 依赖:

<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.liuzhuoming23</groupId>
<artifactId>server-center</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>

<artifactId>producer-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>producer-client</name>
<description>Demo project for Spring Cloud</description>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

在 application.xml 同级新建 bootstrap.yml 文件,并添加内容:

spring:
cloud:
config:
#服务名称
name: producer-client
#profiles
profile: dev
#分支/tag
label: master
discovery:
#是否从注册中心获取配置服务
enabled: true
#配置服务名称
service-id: config

eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://admin:admin@localhost:8000/eureka/

注意这里的服务名称不能是@pom.artifactId@这种从 pom.xml 中获取参数值的方式,必须配置完整的项目名称。这与 bootstrap.xml 和 application.xml 的加载时机有关,不在此赘述,有兴趣自行了解。
修改 application.yml 为:

server:
port: 8010

spring:
application:
name: @pom.artifactId@

在 controller.PortController 类添加可从配置中心获取的键名称:

package com.github.liuzhuoming23.producerclient.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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* port
*
* @author liuzhuoming
*/
@RestController
@RequestMapping("port")
public class PortController {

@Value("${server.port:0}")
private Integer port;
@Value("${config.name:default}")
private String config;

@GetMapping("{name}")
public String port(@PathVariable String name) {
return config + " | " + name + ": client port | " + port;
}
}

按照 eureka->config->producer-client 的顺序启动项目,并访问http://localhost:8010/port/test,返回结果:
🍺🍐🐮🍺🐎 | test: client port | 8010
说明服务生产者已经从配置中心获取到了配置。

动态刷新外部配置

在 producer-client 项目的 controller.PortController 类添加添加@RefreshScope 注解:

package com.github.liuzhuoming23.producerclient.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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* port
*
* @author liuzhuoming
*/
@RestController
@RequestMapping("port")
@RefreshScope
public class PortController {

@Value("${server.port:0}")
private Integer port;
@Value("${config.name:default}")
private String config;

@GetMapping("{name}")
public String port(@PathVariable String name) {
return config + " | " + name + ": client port | " + port;
}
}

修改 application.yml 为:

server:
port: 8010

spring:
application:
name: @pom.artifactId@

management:
endpoints:
web:
exposure:
#开启actuator监控,其中refresh意思是支持刷新配置
include: refresh,info,health

重启 producer-client 项目,并访问http://localhost:8010/port/test,返回结果:
🍺🍐🐮🍺🐎 | test: client port | 8010
然后修改 git 里面的 config/producer-client-dev.yml 文件为:

config:
name: 🍺🍐🙅‍

在 Postman 用 POST 请求访问http://localhost:8010/actuator/refresh刷新配置,然后再访问http://localhost:8010/port/test,返回结果:
🍺🍐🙅‍ | test: client port | 8010
说明动态刷新外部配置成功。
很多博客有说到利用 git 服务的 Webhook 来实现提交 git 更新自动刷新外部配置,其实就是把http://localhost:8010/actuator/refresh这个刷新地址绑定在 WebHook 上。不过因为内网 doom,没办法进行测试。

服务生产者集群刷新外部配置

启动 8010 和 8011 的两个服务生产者实例,并修改 git 里面的 config/producer-client-dev.yml 文件为:

config:
name: 🍺🍐🍺🍐

在 Postman 用 POST 请求访问http://localhost:8010/actuator/refresh刷新 8010 端口服务的配置,然后再访问http://localhost:8010/port/test,返回结果:
🍺🍐🍺🍐 | test: client port | 8011
然后再直接访问http://localhost:8011/port/test,返回结果:
🍺🍐🙅‍ | test: client port | 8011
可见并未按照服务来刷新整个集群的配置。
之后在 Postman 用 POST 请求访问http://localhost:8011/actuator/refresh刷新 8011 端口服务的配置,然后再访问http://localhost:8011/port/test,返回结果:
🍺🍐🍺🍐 | test: client port | 8011
可见刷新哪个实例的 refresh endpoint,哪个实例的配置才会更新。
在集群实例比较少的时候似乎还凑合。要是集群实例比较多这样一个个刷新配置就十分的麻烦,集群扩增后也要手动添加新的刷新地址,并且因为需要请求的刷新地址比较多,也很难做到利用 git 服务的 WebHook 来自动刷新配置,甚至有漏刷新的可能。所以不建议在线上服务使用。
要是注意观察,可以发现在第一次刷新 8010 端口服务的时候返回的是🍺🍐🍺🍐 | test: client port | 8011,而并不是我们期待的🍺🍐🍺🍐 | test: client port | 8010,看上去似乎是 config 服务将同一个服务后启动的实例的本地配置保存到了动态配置中,并在动态刷新外部配置的时候一并刷新到了其他实例的配置里。虽然目前还未造成影响,但是这个特性也许会在没有使用消息总线的情况下让一些类似于灰度更新的需求变得更加难以实现。


系列文章 #Spring Cloud

(1)前言

(2)注册中心-Eureka

(3)服务间调用-Feign(🔒)

(4)路由中心-Gateway

(5)配置中心-Config

(6)配置中心-Bus

(7)监控中心-Admin

(8)监控中心-Sleuth+Zipkin(🔒)

(9)监控中心-Elasticsearch+Zipkin(🔒)

(10)监控中心-HystrixDashboard+Turbine(🔒)

(11)授权中心-Oauth2+JWT

(12)注册中心/配置中心-Nacos

(13)动态路由-Gateway

(14)授权中心-Oauth2+JWT补全


配置中心-Bus 路由中心-Gateway