Spring Cloud 配置中心 ZooKeeper 入门
1. 概述
在《 ZooKeeper 极简入门》文章中,我们一起完成了 ZooKeeper 的学习,并完成了 ZooKeeper 服务器的搭建。
本文我们来学习 Spring Cloud ZooKeeper 提供的 spring-cloud-zookeeper-config
组件,接入 Zookeeper 作为配置中心,实现服务的统一配置管理。
2. 快速入门
示例代码对应仓库:
labx-26-sc-zookeeper-config-demo
。
本小节,我们会在 ZooKeeper 服务中定义配置,并使用 @ConfigurationProperties
和 @Value
注解,读取该配置。
友情提示:如果胖友看过《芋道 Spring Boot 配置文件入门》的「2. 自定义配置」小节,就会发现本小节是对标这块的内容。
如果没看过,也没关系,艿艿只是提一下而已,嘿嘿。继续往下看即可。
下面,我们来创建一个 labx-26-sc-zookeeper-config-demo
示例项目,进行快速入门。最终项目代码如下图所示:
2.1 引入依赖
在 pom.xml
文件中,主要引入 Spring Cloud ZooKeeper 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">
<parent>
<artifactId>labx-26</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>labx-26-sc-zookeeper-config-demo</artifactId>
<properties>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
</properties>
<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Spring Cloud ZooKeeper Config 相关依赖,将 ZooKeeper 作为配置中心,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>
</dependencies>
</project>
引入 spring-cloud-starter-zookeeper-config
依赖,将 ZooKeeper 作为配置中心,并实现对它的自动配置。
2.2 配置文件
创建 bootstrap.yaml
配置文件,添加 ZooKeeper Config 配置项。配置如下:
spring:
application:
name: demo-application
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
# ZooKeeper 作为配置中心的配置项,对应 ZooKeeperConfigProperties 配置类
config:
root: /config # Zookeeper 数据存储的根节点,默认为 /config
default-context: application # 默认读取 Zookeeper 配置的目录,默认为 application
profile-separator: ',' # 多环境目录分隔符,默认为 ,
watcher:
enabled: true # 是否开启 Watch 功能,监听 Zookeeper 数据的变化。默认为 true,实现实时监听配置的更新
① spring.cloud.zookeeper
配置项,设置 ZooKeeper 客户端的配置,对应 ZooKeeperProperties 配置类。
connect-string
配置项,设置 ZooKeeper 服务器的地址。
② spring.cloud.zookeeper.config
配置项,设置 ZooKeeper Config 配置项,对应 ZooKeeperConfigProperties 配置类。
root
配置项,设置 ZooKeeper 数据存储的根节点,默认为 /config
。稍后,我们来具体演示下哈~
default-context
配置项,读取 Zookeeper 配置的默认目录,默认为 application
。对于 Zookeeper Config 来说,它会读取 Zookeeper /{root}/{spring.application.name}/
和 /{root}/{default-context}/
目录下的节点,作为配置项。如下图所示:
profile-separator
配置项,多环境目录分隔符,默认为 ,
。举个例子,在我们设置 Spring Profile 为 dev
时,会多读取 Zookeeper /{root}/{spring.application.name},dev/
和 /{root}/{default-context},dev/
目录下的节点,作为配置项。
watcher.enabled
配置,设置是否开启 Watch 功能,监听 Zookeeper 数据的变化。默认为 true
,实现实时监听配置的更新。
2.3 创建 Zookeeper 配置项
使用 ZooKeeper Inspector 可视化工具,创建 demo-application
应用的两个配置项:
order.pay-timeout-seconds=60
order.create-frequency-seconds=120
友情提示:图艿艿截错了,按照文字版为准,嘿嘿~
2.4 OrderProperties
创建 OrderProperties 配置类,读取 order
配置项。代码如下:
@Component
@ConfigurationProperties(prefix = "order")
public class OrderProperties {
/**
* 订单支付超时时长,单位:秒。
*/
private Integer payTimeoutSeconds;
/**
* 订单创建频率,单位:秒
*/
private Integer createFrequencySeconds;
// ... 省略 setter/getter 方法
}
① 在类上,添加 @Component
注解,保证该配置类可以作为一个 Bean 被扫描到。
② 在类上,添加 @ConfigurationProperties
注解,并设置 prefix = "order"
属性,这样它就可以读取前缀为 order
配置项,设置到配置类对应的属性上。
2.5 DemoController
创建 DemoController 类,提供测试 @ConfigurationProperties
和 @Value
注入配置的两个 HTTP 接口。代码如下:
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private OrderProperties orderProperties;
/**
* 测试 @ConfigurationProperties 注解的配置属性类
*/
@GetMapping("/test01")
public OrderProperties test01() {
return orderProperties;
}
@Value(value = "${order.pay-timeout-seconds}") // @NacosValue(value = "${order.pay-timeout-seconds}")
private Integer payTimeoutSeconds;
@Value(value = "${order.create-frequency-seconds}") // @NacosValue(value = "${order.create-frequency-seconds}")
private Integer createFrequencySeconds;
/**
* 测试 @Value 注解的属性
*/
@GetMapping("/test02")
public Map<String, Object> test02() {
return new JSONObject().fluentPut("payTimeoutSeconds", payTimeoutSeconds)
.fluentPut("createFrequencySeconds", createFrequencySeconds);
}
}
2.6 DemoApplication
创建 DemoApplication 类,作为应用启动类。代码如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class);
}
}
2.7 简单测试
① 使用 DemoApplication 启动示例应用。在 IDEA 控制台中,可以看到 Zookeeper 相关的日志如下:
// 加载了 ZooKeeper `/config/demo-application` 和 `/config/application` 目录下的节点,作为配置项
2020-06-09 07:55:51.761 INFO 13928 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/config/demo-application'}, BootstrapPropertySource {name='bootstrapProperties-/config/application'}]
友情提示:ZooKeeper Config 会额外加载 ZooKeeper
/config/application
目录下的节点,实现多应用之间的配置共享。
② 使用浏览器,访问 http://127.0.0.1:8080/demo/test01 接口,测试 @ConfigurationProperties
注解的配置属性类,返回结果如下,符合预期:
{
"payTimeoutSeconds": 60,
"createFrequencySeconds": 120
}
② 使用浏览器,访问 http://127.0.0.1:8080/demo/test02 接口,测试 @Value
注解的属性,返回结果如下,符合预期:
{
"payTimeoutSeconds": 60,
"createFrequencySeconds": 120
}
3. 多环境配置
示例代码对应仓库:
labx-26-sc-zookeeper-config-demo
。
在《Spring Boot 配置文件入门》的「6. 多环境配置」中,我们介绍如何基于 spring.profiles.active
配置项,在 Spring Boot 实现多环境的配置功能。
在本文的「2. 快速入门」小节中,我们已经提到,Spring Cloud ZooKeeper Config 在我们设置了 Spring Profile 的值时,会多读取 Zookeeper /{root}/{spring.application.name},{profile}/
和 /{root}/{default-context},{profile}/
目录下的节点,作为配置项。
下面,我们来演示 ZooKeeper Config 的多环境配置的功能。无需重新搭建,直接复用「2. 快速入门」小节的项目即可。
3.1 创建 Zookeeper 配置项
① 创建用于 Profile 为 dev
环境的 Zookeeper /config/demo-application,dev
目录,并创建 server.port
节点的值为 8081。如下图所示:
② 创建用于 Profile 为 prod
环境的 Zookeeper /config/demo-application,prod
目录,并创建 server.port
节点的值为 8084。如下图所示:
这两配置都有 server.port
配置项,用于启动不同端口的服务器。???? 为什么选择 server.port
配置呢?因为 Spring Cloud 项目启动后,从日志中就可以看到生效的服务器端口,嘿嘿~
3.2 简单测试
② 开发环境示例:直接在 IDEA 中,增加 --spring.profiles.active=dev
到 Program arguments 中。如下图所示:
使用 DemoApplication 应用,输出日志如下:
# 省略其它日志...
// 加载了 ZooKeeper 如下目录的配置
// 1. `/config/demo-application,dev`
// 2. `/config/demo-application`
// 3. `/config/application,dev`
// 4. `/config/application`
2020-06-09 08:44:17.338 INFO 15215 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/config/demo-application,dev'}, BootstrapPropertySource {name='bootstrapProperties-/config/demo-application'}, BootstrapPropertySource {name='bootstrapProperties-/config/application,dev'}, BootstrapPropertySource {name='bootstrapProperties-/config/application'}]
# Tomcat 启动日志
2020-06-09 08:44:18.228 INFO 15215 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''
Tomcat 启动在 8081 端口,也符合 dev
环境读取 Zookeeper /config/demo-application,dev
目录下的配置。
③ 生产环境示例:直接在 IDEA 中,增加 --spring.profiles.active=prod
到 Program arguments 中。如下图所示:
使用 DemoApplication 应用,输出日志如下:
# 省略其它日志...
// 加载了 ZooKeeper 如下目录的配置
// 1. `/config/demo-application,prod`
// 2. `/config/demo-application`
// 3. `/config/application,prod`
// 3. `/config/application`
2020-06-09 08:50:25.661 INFO 15375 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/config/demo-application,prod'}, BootstrapPropertySource {name='bootstrapProperties-/config/demo-application'}, BootstrapPropertySource {name='bootstrapProperties-/config/application,prod'}, BootstrapPropertySource {name='bootstrapProperties-/config/application'}]
# Tomcat 启动日志
2020-06-09 08:50:26.532 INFO 15375 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8084 (http) with context path ''
Tomcat 启动在 8084 端口,也符合 prod
环境读取 Zookeeper /config/demo-application,prod
目录下的配置。
4. 自动刷新配置
示例代码对应仓库:
labx-26-sc-zookeeper-config-auto-refresh
。
在上面的示例中,我们已经实现从 Zookeeper 读取配置。那么,在应用已经启动的情况下,如果我们将读取的 Zookeeper 的配置进行修改时,应用是否会自动刷新本地的配置呢?
如果是我们上面的示例,答案是部分会。使用 @ConfigurationProperties
注解的会,使用 @Value
注解的不会。
下面,我们从「2. 快速入门」小节的 labx-26-sc-zookeeper-config-demo
项目,复制出 labx-26-sc-zookeeper-config-auto-refresh
项目,用于演示 Zookeeper 的自动刷新配置的功能。最终项目代码如下图所示:
4.1 简单测试
① 使用 DemoApplication 启动示例应用。
② 获得 Zookeeper /config/demo-application
目录的配置内容为:
order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 120 # 订单创建频率,单位:秒
使用 curl
命令,请求 DemoController 提供的两个测试接口,过程如下:
# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":120}
# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"payTimeoutSeconds":60,"createFrequencySeconds":120}
③ 修改 Zookeeper /config/demo-application
目录的配置内容为:
order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 480 # 订单创建频率,单位:秒
此时,我们可以看到 IDEA 控制台打印出了 order.create-frequency-seconds
配置项发生变化的日志,内容如下:
2020-06-09 08:30:44.793 INFO 16369 --- [tor-TreeCache-0] o.s.c.e.event.RefreshEventListener : Refresh keys changed: [order.create-frequency-seconds]
使用 curl
命令,请求 DemoController 提供的两个测试接口,过程如下:
# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":480}
# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"payTimeoutSeconds":60,"createFrequencySeconds":120}
- 使用
@ConfigurationProperties
注解的成功刷新,使用@Value
注解的失败刷新。
4.2 @RefreshScope
在 Spring Cloud 中,提供了 @RefreshScope
注解,可以声明在 Bean 上,实现该 Bean 的配置刷新。
友情提示:对
@RefreshScope
注解的实现原理感兴趣的胖友,可以阅读《@RefreshScope 那些事》文章。
下面,我们将 @RefreshScope
注解添加在 DemoController 类上,实现 @Value
注解的属性的刷新。代码如下:
@RestController
@RequestMapping("/demo")
@RefreshScope // 加我加我加我!
public class DemoController {
// ... 省略其它代码
}
4.3 重新测试
① 使用 DemoApplication 启动示例应用。
② 获得 Zookeeper /config/demo-application
目录的配置内容为:
order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 480 # 订单创建频率,单位:秒
使用 curl
命令,请求 DemoController 提供的两个测试接口,过程如下:
# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":480}
# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"payTimeoutSeconds":60,"createFrequencySeconds":480}
③ 修改 Zookeeper /config/demo-application
目录的配置内容为:
order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 960 # 订单创建频率,单位:秒
使用 curl
命令,请求 DemoController 提供的两个测试接口,过程如下:
# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":960}
# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"payTimeoutSeconds":60,"createFrequencySeconds":960}
- 使用
@ConfigurationProperties
注解的成功刷新,使用@Value
注解的成功刷新。完美~
4.4 EnvironmentChangeEvent
通过 @ConfigurationProperties
或者 @Value
+ @RefreshScope
注解,已经能够满足我们绝大多数场景下的自动刷新配置的功能。但是,在一些场景下,我们仍然需要实现对配置的监听,执行自定义的逻辑。
例如说,当数据库连接的配置发生变更时,我们需要通过监听该配置的变更,重新初始化应用中的数据库连接,从而访问到新的数据库地址。
又例如说,当日志级别发生变更时,我们需要通过监听该配置的变更,设置应用中的 Logger 的日志级别,从而后续的日志打印可以根据新的日志级别。
在 Spring Cloud 中,在 Environment 的属性配置发生变化时,会发布 EnvironmentChangeEvent 事件。这样,我们只需要实现 EnvironmentChangeEvent 事件的监听器,就可以进行自定义的逻辑处理。
例如说,Spring Cloud 内置了 LoggingRebinder 监听器,实现了对 EnvironmentChangeEvent 事件的监听,在发现 logging.level
配置项发生变化时,重新设置日志组件的日志级别。如下图所示:
下面,我们来实现一个自定义的 DemoEnvironmentChangeListener 监听器,打印下变化配置项的最新值。代码如下:
@Component
public class DemoEnvironmentChangeListener implements ApplicationListener<EnvironmentChangeEvent> {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ConfigurableEnvironment environment;
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
for (String key : event.getKeys()) {
logger.info("[onApplicationEvent][key({}) 最新 value 为 {}]", key, environment.getProperty(key));
}
}
}
补充:最近看了下文档,可以通过
@RefreshScope
注解,实现数据源的动态变化,具体可以看看《SpringCloud 运行时刷新数据源相关配置》文章。也就是说,不一定需要通过实现对 EnvironmentChangeEvent 事件的监听处理。
4.5 再次测试
① 使用 DemoApplication 启动示例应用。
② 修改 Zookeeper /config/demo-application
目录,增加 test
节点的内容为 true
。此时在 IDEA 控制台可以看到 DemoEnvironmentChangeListener 打印的日志如下:
2020-06-09 08:37:15.226 INFO 16369 --- [tor-TreeCache-0] .i.s.l.z.l.DemoEnvironmentChangeListener : [onApplicationEvent][key(test) 最新 value 为 true]
5. 配置加密
考虑到安全性,我们可能最好将配置文件中的敏感信息进行加密。例如说,MySQL 的用户名密码、第三方平台的 Token 令牌等等。不过,Zookeeper Config 暂时未内置配置加密的功能。官方文档说明如下:
因此,我们暂时只能在客户端进行配置的加解密。这里,我们继续采用在《芋道 Spring Boot 配置文件入门》的「8. 配置加密」小节中使用的 Jasypt。
推荐阅读
-
spring cloud Eureka 配置信息
-
Spring Cloud Config 配置中心实践过程中,你需要了解这些细节!
-
详解Spring Cloud Eureka多网卡配置总结
-
详解基于Spring Cloud几行配置完成单点登录开发
-
Spring Cloud Alibaba | Nacos服务中心初探
-
spring cloud 入门系列八:使用spring cloud sleuth整合zipkin进行服务链路追踪
-
2.1spring cloud 环境配置
-
Spring Cloud Alibaba | Nacos配置管理
-
基于ZooKeeper实现简单的配置中心
-
01Spring基于xml的IOC配置--入门