大纲 配置中心介绍 什么是配置中心 在集中式开发时代,配置文件已经基本足够了,因为那时配置的管理通常不会成为一个很大的问题。但是在互联网时代,应用都是分布式系统,部署在 N 台服务器上,想要去线上一台台地重启机器肯定不靠谱,并且维护成本也很高,所以配置中心应运而生。配置中心被用作集中管理不同环境(Dev、Test、Stage、Prod)和不同集群配置,以及在修改配置后将实时动态推送到应用上进行刷新。配置中心应具备的功能如下:
Open API 业务无关性 高可用集群 配置生效监控 配合灰度与更新 一致性 K-V 存储 统一配置实时推送 配置全局恢复、备份与历史 主流配置中心对比 Spring Cloud Config、Apollo、Nacos 对比图 Spring Cloud Config、Netflix Archaius、Apollo、Disconf(已停止维护) 对比图
Config 配置中心概述 Spring Cloud Config 是一个集中化外部配置的分布式系统,由服务端和客户端组成。它不依赖于注册中心,是一个独立的配置中心。Spring Cloud Config 支持多种存储配置信息的形式,目前主要有 JDBC、Vault、Native、SVN、Git,其中默认为 Git 存储形式,Git 版的工作原理如下图:
配置中心流转与整体支持图
Config 入门案例 1. 版本说明 在本文中,使用的 Spring Cloud 版本是 Finchley.RELEASE,对应的 Spring Boot 版本是 2.0.3,特别声明除外,点击下载 完整的案例代码。
2. 准备工作 由于下面的 Spring Cloud Config 使用 Git 作为存储方式,因此需要提前在 Git 远程仓库(Github、Gitlab)中创建对应的仓库,然后往仓库里 Push 三个配置文件,分别是 config-client-dev.yml、config-client-prod.yml、config-client-test.yml,配置文件的内容如下:
1 2 3 4 5 6 server: port: 9001 cn: springcloud: config: I am the git configuration file from dev environment
1 2 3 4 5 6 server: port: 9002 cn: springcloud: config: I am the git configuration file from prod environment
1 2 3 4 5 6 server: port: 9003 cn: springcloud: config: I am the git configuration file from test environment
3. 创建 Maven 父级 Pom 工程 在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 Maven 配置如下:
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 32 33 34 35 36 37 38 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.0.3.RELEASE</version > </parent > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > </dependencies > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Finchley.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <repositories > <repository > <id > spring-milestones</id > <name > Spring Milestones</name > <url > https://repo.spring.io/libs-milestone</url > <snapshots > <enabled > false</enabled > </snapshots > </repository > </repositories >
4. 创建 Config Server 工程 创建 Config Server 的 Maven 工程,配置工程里的 pom.xml
文件,需要引入 spring-cloud-config-server
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-server</artifactId > </dependency >
创建 Config Server 的主启动类,增加 @EnableConfigServer
注解:
1 2 3 4 5 6 7 8 @EnableConfigServer @SpringBootApplication public class ConfigServerApplication { public static void main (String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
添加 Config Server 需要的 application.yml
配置文件到工程中,其中 uri
指的是 Git 远程仓库的地址,private_key_file
是指 SSH 公钥文件;若 Git 仓库地址使用的是 HTTPS 协议,此时可以使用 usemame
、password
参数替代掉 private_key_file
,两者分别代表 Git 访问的用户名和密码;search-paths
表示搜索特定目录下所有满足条件的配置文件,可以根据需求添加多个目录,目录之间用逗号隔开;label
指的是 Git 仓库的分支名称,如果不写,默认的分支为 master
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server: port: 8001 spring: application: name: config-server cloud: config: server: git: uri: git@github.com:xxxxx/spring-cloud-config-study-repo.git search-paths: spring-cloud-config-study-repo/ strictHostKeyChecking: false private_key_file: /root/.ssh/id_rsa.pub label: master
启动 Config Server 应用后,可以看到控制台会输出如下信息,这里只是截取关键信息,从控制台信息里的 Mapped 中可以看到配置信息和 URL 的映射关系;其中 name
是应用名称,也可以理解成 Git 仓库里配置文件的名称,profile
指的是对应激活的环境名,例如 dev、test、prod 等,label
指的是 Git 的分支
1 2 3 4 5 6 7 8 9 10 Mapped "{[/{name}-{profiles}.yml || /{name}-{profiles}.yaml],methods=[GET]}" Mapped "{[/{name}/{profiles:.*[^-].*}],methods=[GET]}" Mapped "{[/{name}/{profiles}/{label:.*}],methods=[GET]}" Mapped "{[/{label}/{name}-{profiles}.properties],methods=[GET]}" Mapped "{[/{name}-{profiles}.json],methods=[GET]}" Mapped "{[/{label}/{name}-{profiles}.json],methods=[GET]}" Mapped "{[/{label}/{name}-{profiles}.yml || /{label}/{name}-{profiles}.yaml],methods=[GET]}" Mapped "{[/{name}/{profile}/**],methods=[GET],params=[useDefaultLabel]}" Mapped "{[/{name}/{profile}/{label}/**],methods=[GET]}" Mapped "{[/{name}/{profile}/{label}/**],methods=[GET],produces=[application/octet-stream]}"
通过 http://127.0.0.1:8001/config-client/dev/master
访问 Config Server,接口返回的结果如下
此时观察 Config Server 控制台打印的信息可知,Config Server 会在本地的临时目录下面克隆远程仓库中的配置文件,本地临时目录的路径如下:
1 o.s.c.c.s.e.NativeEnvironmentRepository : Adding property source: file:/tmp/config-repo-3558022506897899775/application.yml (document #0)
5. 创建 Config Client 工程 创建 Config Client 的 Maven 工程,配置工程里的 pom.xml
文件,需要引入 spring-cloud-config-client
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency >
创建 Config Client 的主启动类:
1 2 3 4 5 6 7 @SpringBootApplication public class ConfigClientApplication { public static void main (String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } }
为了更好地观察拉取到的 Git 上面的配置,这里需要创建一个 Controller 用于访问返回配置信息,同时还需要创建一个实体,用于注入远程配置上的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component @ConfigurationProperties(prefix = "cn.springcloud") public class ConfigProperties { private String config; public String getConfig () { return config; } public void setConfig (String config) { this .config = config; } }
1 2 3 4 5 6 7 8 9 10 11 @RestController public class ConfigController { @Autowired public ConfigProperties configProperties; @GetMapping("/getConfigInfo") public String getConfigInfo () { return configProperties.getConfig(); } }
添加 Config Client 需要的 application.yml
配置文件到工程中:
1 2 3 spring: application: name: config-client
添加 Config Client 需要的 bootstrap.yml
配置文件到工程中,这些配置为什么要放在 bootstrap.yml
里,而不放在 application.yml
中呢?这与 Spring Boot 的加载顺序有关,bootstrap.yml
文件会优先于 application.yml
加载,因此会去加载远程的配置文件信息
1 2 3 4 5 6 7 spring: cloud: config: name: config-client profile: dev label: master uri: http://127.0.0.1:8001
6. 测试结果 依次启动 config-server、config-client 应用 访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment
更改 Git 远程仓库中的 config-client-dev.yml
配置文件,将内容修改为 I am the git configuration file from dev environment updated
重启 config-client 应用,再次访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment updated
,说明配置更新了 刷新配置中心信息 Config Client 手动刷新 为了不用重启 Congit Client 应用也可以获取到最新的配置信息,下面将讲解在 Congit Client 端如何手动刷新配置信息,点击下载 完整的案例代码。
i. 准备工作 本示例用到上面入门案例中 Git 仓库里的配置文件,包括 config-client-dev.yml、config-client-prod.yml、config-client-test.yml。
ii. 创建 Config Server 工程 由于本示例是在上面的入门案例的基础上进行改造的,因此 Config Server 工程与上面入门案例中的 Config Server 工程完全一样,只需拷贝一份即可,由于篇幅有限,这里不再累述。
iii. 创建 Config Client 工程 创建 Config Client 的 Maven 工程,配置工程里的 pom.xml
文件,第二个依赖是端点的访问依赖,第三个依赖是安全的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</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-security</artifactId > </dependency >
添加 Config Client 需要的 application.yml
配置文件到工程中,management.endpoints.web.exposure.include=*
表示暴露所有端点,默认情况下只暴露 info、health 端点,management.endpoint.health.show-details=always
表示总是显示详细信息
1 2 3 4 5 6 7 8 9 10 11 spring: application: name: config-client management: endpoints: web: exposure: include: "*" health: show-details: always
添加 Config Client 需要的 bootstrap.yml
配置文件到工程中:
1 2 3 4 5 6 7 spring: cloud: config: name: config-client profile: dev label: master uri: http://127.0.0.1:8001
往 Config Client 添加安全配置类,主要作用是关闭端点访问的安全校验:
1 2 3 4 5 6 7 8 @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.csrf().disable(); } }
为了更好地观察拉取到的 Git 上面的配置,这里需要创建一个 Controller 用于访问返回配置信息,同时还需要创建一个实体,用于注入远程配置上的信息。注意,这里的 ConfigProperties 与 ConfigController 类都需要额外添加 @RefreshScope
注解,被 @RefreshScope
注解修饰的 Bean 都是延迟加载的,只有在第一次访问时才会被初始化;刷新 Bean 也是同理,刷新后下次访问会创建一个新的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component @RefreshScope public class ConfigProperties { @Value("${cn.springcloud.config}") private String config; public String getConfig () { return config; } public void setConfig (String config) { this .config = config; } }
1 2 3 4 5 6 7 8 9 10 11 12 @RefreshScope @RestController public class ConfigController { @Autowired public ConfigProperties configProperties; @GetMapping("/getConfigInfo") public String getConfigInfo () { return configProperties.getConfig(); } }
iiii. 测试结果 依次启动 config-server、config-client 应用 访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment
更改 Git 远程仓库中的 config-client-dev.yml
配置文件,将内容修改为 I am the git configuration file from dev environment updated
通过 Post 请求访问 http://127.0.0.1:9001/actuator/refresh
,让 Config Client 刷新配置信息 再次访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment updated
,说明配置更新了 结合 Spring Cloud Bus 热更新 Spring Cloud Config 结合 Spring Cloud Bus 进行刷新的整体流程图如下,当用户更新配置信息时,触发 Git Hook 配置地址的调用,Config Server 接收到 Refresh 请求后,通过 Bus 将消息发送到 Config Client,当 Config Client 接收到消息后会重新发送请求加载配置信息,大体流程就是这样。下面将使用 RabbitMQ 作为消息中间件,由于篇幅有限,这里不再讲解 RabbitMQ 的安装和使用方法,点击下载 完整的案例代码。
1. 准备工作 本示例用到上面入门案例中 Git 仓库里的配置文件,包括 config-client-dev.yml、config-client-prod.yml、config-client-test.yml。
2. 创建 Config Server 工程 创建 Config Server 的 Maven 工程,配置工程里的 pom.xml
文件,第二个依赖是端点的访问依赖,第三个依赖是安全的依赖,第四个是消息中间件的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-server</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-security</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bus-amqp</artifactId > </dependency >
创建 Config Server 的主启动类,增加 @EnableConfigServer
注解:
1 2 3 4 5 6 7 8 @EnableConfigServer @SpringBootApplication public class ConfigServerApplication { public static void main (String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
创建 Config Server 的安全配置类,主要作用是关闭端点访问的安全校验:
1 2 3 4 5 6 7 8 @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.csrf().disable(); } }
添加 Config Server 需要的 application.yml
配置文件到工程中,其中包括 RabbitMQ 的地址和账号信息,spring.cloud.bus.trace.enabled=true
表示开启消息跟踪:
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 server: port: 8001 spring: application: name: config-server rabbitmq: port: 5672 host: localhost username: admin password: admin cloud: bus: trace: enabled: true config: server: git: uri: git@github.com:xxxxx/spring-cloud-config-study-repo.git search-paths: spring-cloud-config-study-repo/ strictHostKeyChecking: false private_key_file: /root/.ssh/id_rsa.pub label: master management: endpoints: web: exposure: include: "*" health: show-details: always
3. 创建 Config Client 工程 创建 Config Client 的 Maven 工程,配置工程里的 pom.xml
文件,第二个依赖是端点的访问依赖,第三个依赖是安全的依赖,第四个是消息中间件的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</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-security</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bus-amqp</artifactId > </dependency >
往 Config Client 添加安全配置类,主要作用是关闭端点访问的安全校验:
1 2 3 4 5 6 7 8 @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.csrf().disable(); } }
为了更好地观察拉取到的 Git 上面的配置,这里需要创建一个 Controller 用于访问返回配置信息,同时还需要创建一个实体,用于注入远程配置上的信息;注意,这里的 ConfigProperties 与 ConfigController 类都需要额外添加 @RefreshScope
注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component @RefreshScope public class ConfigProperties { @Value("${cn.springcloud.config}") private String config; public String getConfig () { return config; } public void setConfig (String config) { this .config = config; } }
1 2 3 4 5 6 7 8 9 10 11 12 @RefreshScope @RestController public class ConfigController { @Autowired public ConfigProperties configProperties; @GetMapping("/getConfigInfo") public String getConfigInfo () { return configProperties.getConfig(); } }
添加 Config Client 需要的 application.yml
配置文件到工程中,其中包括 RabbitMQ 的地址和账号信息,spring.cloud.bus.trace.enabled=true
表示开启消息跟踪:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: application: name: config-client rabbitmq: port: 5672 host: localhost username: admin password: admin cloud: bus: trace: enabled: true management: endpoints: web: exposure: include: "*" health: show-details: always
添加 Config Client 需要的 bootstrap.yml
配置文件到工程中:
1 2 3 4 5 6 7 spring: cloud: config: name: config-client profile: dev label: master uri: http://127.0.0.1:8001
4. 测试结果 依次启动 config-server、config-client 应用 访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment
更改 Git 远程仓库中的 config-client-dev.yml
配置文件,将内容修改为 I am the git configuration file from dev environment updated
通过 Post 请求访问 http://127.0.0.1:8001/actuator/bus-refresh
,让 Config Server 通过 Spring Cloud Bus 发送消息通知所有 Config Client 刷新配置信息 再次访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment updated
,说明配置更新了 提示:可以将 Spring Cloud Bus 的刷新地址配置在 WebHooks 上面,这样在 Git 仓库每次有新文件提交(Push)之后,所有 Config Client 都会自动执行刷新的动作 Config Client 自动刷新(任务调度) 在有些应用上面,不需要在服务端批量推送消息的时候,客户端本身需要获取参数变化的情况,此时可以使用客户端的自动刷新功能,其原理是使用任务调度执行刷新操作,点击下载 完整的案例代码
1. 准备说明 本示例用到上面入门案例中 Git 仓库里的配置文件,包括 config-client-dev.yml、config-client-prod.yml、config-client-test.yml。
2. Config Refresh Autoconfig 工程 创建 Config Refresh Autoconfig 的 Maven 工程,配置工程里的 pom.xml 文件:
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-autoconfigure</artifactId > </dependency >
创建 Config Refresh Autoconfig 工程里的自动配置类,添加相关注解,使其在 Spring Boot 启动的时候将其加载。在该类中,主要是注入了端点类,通过定时任务和刷新时间,进行配置请求刷新。由于在类中是直接调用了 RefreshEndpoint 的 refresh()
方法,所以对于 F 版的安全机制不需要对端点进行打开也可以,但需要依赖 spring-boot-starter-actuator
,否则无法注入 RefreshEndpoint 的 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @ConditionalOnClass(RefreshEndpoint.class) @ConditionalOnProperty("spring.cloud.config.refreshInterval") @AutoConfigureAfter(RefreshAutoConfiguration.class) @Configuration public class ConfigAutoRefreshConfiguration implements SchedulingConfigurer { private static final Logger logger = LoggerFactory.getLogger(ConfigAutoRefreshConfiguration.class); @Value("${spring.cloud.config.refreshInterval}") private long refreshInterval; @Autowired private RefreshEndpoint refreshEndpoint; @Override public void configureTasks (ScheduledTaskRegistrar scheduledTaskRegistrar) { final long interval = getRefreshIntervalInMilliseconds(); logger.info(String.format("Scheduling config refresh task with %s second delay" , refreshInterval)); scheduledTaskRegistrar.addFixedDelayTask(new IntervalTask(new Runnable() { @Override public void run () { refreshEndpoint.refresh(); } }, interval, interval)); } private long getRefreshIntervalInMilliseconds () { return refreshInterval * 1000 ; } @ConditionalOnMissingBean(ScheduledAnnotationBeanPostProcessor.class) @EnableScheduling @Configuration protected static class EnableSchedulingConfigProperties { } }
在 Config Refresh Autoconfig 工程里创建 /src/main/resources/META-INF/spring.factories
配置文件,添加上面的自动配置类:
1 2 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.springcloud.study.config.ConfigAutoRefreshConfiguration
3. Cofnig Client 工程 这里 Cofnig Client 工程的代码基本与上面的 “Config Client 手动刷新” 示例的代码一致,拷贝一份即可,这里不再累述。
Cofnig Client 工程的 pom.xml
文件,引入 config-refresh-autoconfig
依赖:
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency > <dependency > <groupId > com.springcloud.study</groupId > <artifactId > config-refresh-autoconfig</artifactId > <version > 1.0-SNAPSHOT</version > </dependency >
Cofnig Client 工程的 application.yml
文件,refreshInterval: 15
表示每 15 秒刷新一次配置信息:
1 2 3 4 5 6 spring: application: name: config-client cloud: config: refreshInterval: 15
4. Cofnig Server 工程 这里 Cofnig Server 工程的代码基本与上面的 “Config Client 手动刷新” 示例的代码一致,拷贝一份即可,这里不再累述。
5. 测试结果 依次启动 config-server、config-client 应用 访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment
更改 Git 远程仓库中的 config-client-dev.yml
配置文件,将内容修改为 I am the git configuration file from dev environment updated
等待一段时间后(15 秒),再次访问 http://127.0.0.1:9001/getConfigInfo
,接口会返回 I am the git configuration file from dev environment updated
,说明配置更新了