Skip to content

Commit

Permalink
Create 24. 第二十四节:分布式配置客户端设计.md
Browse files Browse the repository at this point in the history
  • Loading branch information
mercyblitz committed May 14, 2023
1 parent 43c6028 commit a6a1c6c
Showing 1 changed file with 304 additions and 0 deletions.
304 changes: 304 additions & 0 deletions stage-2/docs/24. 第二十四节:分布式配置客户端设计.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
> <a name="oAod9"></a>
#### 主要内容
> - 配置操作:理解配置客户端通过 Open API 操作配置的设计
> - 生态整合:将配置客户端 API 整合业界成熟的框架,如 Spring 配置、MicroProfile 配置等
> - 配置验证:实现客户端与服务端的配置版本控制,提供合法性校验等手段,确保客户端配置是合法有效的
<a name="JQpiM"></a>
# 配置操作
<a name="PVhPN"></a>
## Open API
基于 REST 来开发,构建在 HTTP 协议上(版本化)<br />gRPC 的流行,不排除未来基于 HTTP 2.0 协议来开发<br />Dubbo 整合 gRPC

<a name="yjC7R"></a>
## 参考实现
<a name="NqWxF"></a>
### Alibaba Nacos
[https://nacos.io/zh-cn/docs/open-api.html](https://nacos.io/zh-cn/docs/open-api.html)
<a name="ZudIc"></a>
### Ctrip Apollo
[https://www.apolloconfig.com/#/zh/usage/apollo-open-api-platform](https://www.apolloconfig.com/#/zh/usage/apollo-open-api-platform)
<a name="tRTvI"></a>
### Hashicorp Consul
[https://developer.hashicorp.com/consul/api-docs/config](https://developer.hashicorp.com/consul/api-docs/config)

<a name="oi1hY"></a>
### 特性分类
<a name="JJfnc"></a>
#### 功能特性
<a name="voYpN"></a>
##### 发布配置
<a name="KvglV"></a>
##### 修改配置
<a name="g29Xu"></a>
##### 删除配置
<a name="TjqUK"></a>
##### 加载配置
<a name="e7pm2"></a>
##### 列举配置
<a name="Or6yK"></a>
##### 配置更新通知
<a name="KchzX"></a>
#### 非功能特性
<a name="yqQhv"></a>
##### 缓存 - Caching
<a name="Hh7XU"></a>
##### 安全 - ACL
<a name="DXhQ0"></a>
##### 指标 - Metrics
<a name="PHPeD"></a>
##### 日志 - Logging

<a name="BHKUJ"></a>
# 生态整合
<a name="OevY5"></a>
## Spring Environment Abstract
<a name="AEKvS"></a>
### Spring Profiles
配置化条件(运行时条件)
<a name="G3jQZ"></a>
### Spring PropertySourcesPropertyResolver
PropertySourcesPropertyResolver = (PropertyResolver + PropertySources)<br />属性配置(Properties)-> 用于 Spring Bean 属性(Properties)
<a name="neXEs"></a>
#### 配置处理 - PropertyResolver
配置处理 = 配置读取 + 展位替换处理(可嵌套) + 类型转换
<a name="ybApk"></a>
##### 注解注入 - @Value
<a name="BldvO"></a>
##### API 读取 - PropertyResolver#getProperty
<a name="roUCN"></a>
##### XML 占位符 - ${...}
<a name="HjT7Y"></a>
#### 配置来源 - PropertySources
由多个 PropertySource 组成,有序(优先级)
<a name="XIOXu"></a>
##### 实现类 - MutablePropertySources
内部存储一个 PropertySource CopyOnWriteArrayList

<a name="pMa3l"></a>
#### 类型转换
<a name="IxjF1"></a>
##### 类型转换服务 - org.springframework.core.convert.ConversionService
<a name="pVOYR"></a>
##### 类型转换器 - org.springframework.core.convert.converter.Converter

<a name="Q7vNt"></a>
#### 配置变化事件监听(依赖 Spring Cloud Context)
<a name="nn48Y"></a>
##### 属性配置变更事件 - EnvironmentChangeEvent
事件驱动内容需要只读(快照)。<br />不足:

- 类命名 - EnvironmentChangeEvent -> ConfigurationPropertiesChangeEvent
- 缺少配置值 -
- keys:变更 Property Keys
- (缺少)oldValues:历史配置值
- (缺少)newValues:新的配置值
```java
Environment environment = ...;
EnvironmentChangeEvent event = ...;
Set<String> keys = event.getKeys();
for(String key : keys){
// 当前配置值可能与 EnvironmentChangeEvent 事件发生时发生改变
String value = environment.getProperty(key);
}
```
<a name="V6JIx"></a>
## MicroProfile Config
<a name="H2800"></a>
### 规范
3.0 - [https://download.eclipse.org/microprofile/microprofile-config-3.0/microprofile-config-spec-3.0.html](https://download.eclipse.org/microprofile/microprofile-config-3.0/microprofile-config-spec-3.0.html)<br />参考实现:[https://github.com/mercyblitz/geekbang-lessons/tree/master/projects/stage-1/middleware-frameworks/my-configuration](https://github.com/mercyblitz/geekbang-lessons/tree/master/projects/stage-1/middleware-frameworks/my-configuration)
<a name="sP3nH"></a>
### 配置 Profiles
<a name="qRUtA"></a>
### 属性配置
<a name="PN6R2"></a>
#### 配置提供者 - org.eclipse.microprofile.config.spi.ConfigSourceProvider
一个或多个 ConfigSource
<a name="MYW9E"></a>
#### 单个配置源 - org.eclipse.microprofile.config.spi.ConfigSource
<a name="rRFcf"></a>
#### 配置处理
<a name="Cmh9x"></a>
##### API 配置获取 - org.eclipse.microprofile.config.spi.ConfigSource#getValue
<a name="EI9Iz"></a>
##### 注解配置获取 - @Inject + @ConfigProperty
<a name="KIQ0Z"></a>
#### 类型转换 - org.eclipse.microprofile.config.spi.Converter

<a name="XzCtp"></a>
## Alibaba Naocs
<a name="PDXlX"></a>
### Nacos Client
Nacos Client 对 Nacos Open API 支持<br />Nacos Client 本地缓存<br />Nacos Client 支持配置变更
<a name="spAzl"></a>
### Nacos Spring
<a name="oWVhs"></a>
#### Nacos Spring 的 @Value 注解行为
@Value 行为与 Spring Framework 保持一致
```java
@Value("${my.name}"
private String name;

// 当 my.name 初始配置为 "abc" ,name 属性内容为 "abc"
// 当 my.name 配置变更为 "def" ,name 属性内容仍为 "abc"
```
如果需要支持配置变更的话,可使用 @NacosValue,并且可控制是否需要动态变更。
```java
@NacosValue(value = "${useLocalCache:false}", autoRefreshed = true)
private boolean useLocalCache;
```
假设 @NacosValue 设置 autoRefreshed = false 时,它与 Spring @Value 行为是相同的。但是 @NacosValule 注解来自于 Nacos Client,它有可能在 Java EE 环境被实现。
> 注:配置注解不要绑定在某个具体实现上,比如 Spring Framework
> 假设 @AbcValue 仅绑定在 Spring Framework 上,它可以如下实现:

```java
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Value
public @interface AbcValue {

/**
* The actual value expression: e.g. "#{systemProperties.myProp}".
*
* @return value expression
*/
@AliasFor(value = "value" , annotation = Value.class)
String value();

// 其他扩展属性
}
```
<a name="Y6MNP"></a>
## Ctrip Apollo
<a name="NKT65"></a>
### Apollo Client
Apollo ClientApollo Open API 支持<br />Apollo Client 本地缓存<br />Apollo Client 支持配置变更

<a name="TYMQs"></a>
#### 模块依赖

- apollo-client
- apollo-core

<a name="MMZsM"></a>
#### 版本分布
<a name="VKEaw"></a>
##### apollo-client
通常 Java 应用 接入的 Apollo 客户端
<a name="a6rX3"></a>
##### apollo-client-config-data
Apollo 客户端 Spring Boot 2.4 的支持

<a name="VAGmg"></a>
#### 监听配置变化事件
<a name="cIXbu"></a>
##### 事件 - ConfigChangeEvent
<a name="oryRx"></a>
##### Spring 事件 - ApolloConfigChangeEvent
基于 Spring 事件 - ApplicationEvent 扩展,不过可以使用 PayloadApplicationEvent 替代。<br />ApolloConfigChangeEvent 包装了 ConfigChangeEvent 对象:
```java
public class ApolloConfigChangeEvent extends ApplicationEvent {

public ApolloConfigChangeEvent(ConfigChangeEvent source) {
super(source);
}

public ConfigChangeEvent getConfigChangeEvent() {
return (ConfigChangeEvent) getSource();
}
}
```
Spring 事件监听器时,可以如下:
```java
@EventListener(ApolloConfigChangeEvent.class)
public void onApolloConfigChangeEvent(ApolloConfigChangeEvent event){
ConfigChangeEvent configChangeEvent = event.getConfigChangeEvent();
}
```
实际上,可以简化为如下:
```java
@EventListener(ConfigChangeEvent.class)
public void onApolloConfigChangeEvent(ConfigChangeEvent event){
// 前提是 ConfigChangeEvent 作为 Spring PayloadApplicationEvent 对象传输:
// ApplicationEventPublisher#publish(ConfigChangeEvent)
}
```

<a name="oBF5X"></a>
##### 监听器 - ConfigChangeListener
<a name="DSvMv"></a>
##### 异步事件监听
基于线程池
```java
static {
m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory
.create("Config", true));
}
```

- 优点
- 异步
- 性能相对高
- ConfigChangeListener 不会相互影响
- 不足
- 消费线程使用随着 Listener 增多而增多
- 不支持自定义的超时,默认使用 Executors.newCachedThreadPool 的 Keep-Live 60

<a name="f9WjK"></a>
#### 配置优化
<a name="Bevv7"></a>
##### APP ID 配置
```properties
app.id = ${spring.application.name} # 最低优先级
```
<a name="mpfMW"></a>
##### SpringValue 优化

1. SpringValue 对象是否需要使用 WeakReference<Object> 作为 Bean 引用?
```java
public class User {

@Value("${my.name}"
private String user;

}
```
假设 User Bean Scope"singleton" 的话,那么该 Bean 会被 Spring IoC 容器托管,并且管理其生命周期,也就是在 Spring IoC 容器生命周期类,它是一个强引用,所以 WeakReference<Object> 引用该 Bean 是没有必要的。<br />假设 User Bean Scope 为 prototype 的话,那么该 Bean 初始化后,就不被 Spring IoC 容器托管。该被存在两种情况:

- 一次性使用,类似于 Request 对象,SpringValue 对象也会一次性创建,WeakReference<Object> 作为 SpringValue 对象成员,它不会长期使用 Spring Bean,所以没有必要配置更改。
- 非一次性使用,该 Bean 对象将被另外一个容器对象持久
- 如果容器对象也是一次性的话,该 Bean 也会一次性的。
- 如果容器对象不是一次性的话,该 Bean 也会被尝试驻留 JVM Heap

综上所述,WeakReference<Object> 似乎没有必要设计。

2. SpringValue@Value 支持优先
- 支持
- 字段注入 - Field
- 属性注入 - Setter 方法
- @Value 还有方法注入
```java
public void init(@Value("${my.name}" name, @Value("${my.age}" age) {

}
```
可参考 @Value 实现类 - AutowiredAnnotationBeanPostProcessor
<a name="xj7ZI"></a>
### Apollo Spring
<a name="uuK6f"></a>
#### Apollo Spring@Value 注解行为
@Value 支持配置动态变更,调整 Spring @Value 注解行为:
```java
@Value("${my.name}"
private String name;

// 当 my.name 初始配置为 "abc" ,name 属性内容为 "abc"
// 当 my.name 配置变更为 "def" ,name 属性内容变为 "def"
```

<a name="wExgS"></a>
# 作业:配置验证
[https://github.com/mercyblitz/java-training-camp/issues/16](https://github.com/mercyblitz/java-training-camp/issues/16)<br />


0 comments on commit a6a1c6c

Please sign in to comment.