netflix从数据源获取动态配置的框架
1 从apache.configuration到netfilx
apache.commons.configuration包是apache提供的一套解析properties文件的能力。其中提供了一个抽象类AbstractConfiguration。其中有很多实现,例如:
- PropertiesConfiguration:commons包提供的一个经典的配置加载器。实现AbstractFileConfiguration,提供了从文件中加载配置的能力
- ConcurrentMapConfiguration:netflix提供的实现,内部使用了一个ConcurrentHashMap,用于并发场景线程安全和吞吐量
- DynamicConfiguration:ConcurrentMapConfiguration的子类,是netflix动态配置对apache.commons的实现,提供动态从数据源获取所有配置值的功能,通过轮询数据源更新配置值
简单看下ConcuurentMapConfiguration
构造方法
public ConcurrentMapConfiguration(Map<String, Object> mapToCopy)
public ConcurrentMapConfiguration(Configuration config)
提供了两种工作方法,使用Map进行构造,以及使用Configuration进行构造。这里的Configuration可以使用PropertiesConfiguration,而PropertiesConfiguration继承自AbstractFileConfiguration,提供了从文件中加载配置的能力。
如果使用AbstractFileConfiguration进行构造,有以下流程:
public ConcurrentMapConfiguration(Configuration config) {
……
Object value = config.getProperty(name);
map.put(name, value);
}
-- AbstractFileConfiguration#getProperty --
reload();
return super.getProperty(key)
从方法字面含义上也可以看出来,这里是做了对文件的重新加载(存疑,测试过程中实际没有生效)。
addProperty、setProperty
这两个方法是添加属性用的
fireEvent(EVENT_SET_PROPERTY, key, value, true);
可以看到这里做了事件广播
fireEvent
for (ConfigurationListener l: listeners)
……
l.configurationChanged(event);
重写了apache.commons中的EventSource类。EventSource是提供动态变化的广播能力的。这里遍历所有的ConfigurationListener,调用configurationChanged方法。而Listener的添加点在addConfigurationListener方法。
可以看到,netflix的动态配置基于apache.commons能力的扩展的。但是这就很不方便,如果apache后续不给用了,ConcurrentMapConfiguration就不能用了。netflix重头换底层能力是比较麻烦的,因此就有了DynamicPropertySupport类,对AbstractConfiguration做一层封装。
2 DynamicPropertySupport - 动态配置顶层接口
String getString(String propName);
void addConfigurationListener(PropertyListener expandedPropertyListener);
提供了两个接口方法。针对apache.commons,做了一个实现:ConfigurationBackedDynamicPropertySupportImpl,功能和基于apache的ConcurrentMapConfiguration基本一致
构造函数
public ConfigurationBackedDynamicPropertySupportImpl(AbstractConfiguration config)
addConfigurationListener
ExpandedConfigurationListenerAdapter nl = new
ExpandedConfigurationListenerAdapter(expandedConfigListener);
config.addConfigurationListener(nl);
其实就是基于ConcurrentMapConfiguration的add方法进行添加,但是首先构造了一个ExpandedConfigurationListenerAdapter,这是一个适配器,将netflix自己的Listerner类包装成apache.commons适配的类型,同时持有自己这个类的引用。使用适配器,实现了netflix代码和apache.common的解耦,如果需要更新底层代码,只需要增加新的适配器即可。
使用DynamicPropertySupport
public void test() throws ConfigurationException {
PropertiesConfiguration configuration = new PropertiesConfiguration("app.properties");
DynamicPropertySupport dynamicPropertySupport = new
ConfigurationBackedDynamicPropertySupportImpl(configuration);
dynamicPropertySupport.addConfigurationListener(new AbstractDynamicPropertyListener(){
@Override
public void handlePropertyEvent(String name, Object value, EventType eventType) {
System.out.println("name:" + name);
System.out.println("value:" + value);
System.out.println("EventType:" + eventType.name());
}
});
configuration.setProperty("test.name" , "coredy");
}
这里可以直接使用DynamicPropertySupport,但是提到过,这个Support是为了与apache.common解耦从而抽象出来的一层封装,使用这个东西,用户还是需要用到apache.commons包,不方便,因此还要继续向上抽象,提供一个用户不感知底层实现的入口,即DynamicPropertyFactory
netflix创建监听变化的动态配置的基础框架
1 DynamicPropertyFactory
该工厂创建动态属性的实例并将其与基础配置或关联,其中可以在运行时动态更改属性。动态属性工厂创建的实际上都是基础类型的动态配置,他们都实现了PropertyWraper。如果想要更复杂的类型,应该给予String类型的DynamicStringProperty进行进一步演化。
DynamicPropertyFactory instance = DynamicPropertyFactory.getInstance();
DynamicStringProperty stringProperty = instance.getStringProperty("con1", "defalut");
stringProperty.addCallback(() -> System.out.println("con1 属性值发生变化!"));
while (true){
System.out.println(stringProperty.get());
TimeUnit.SECONDS.sleep(10);
}
可以看出来,首先通过DynamicPropertyFactory的getStringProperty方法获取了一个DynamicStringProperty实例,然后调用addCallback方法添加值变动的回调函数。
getStringProperty
DynamicStringProperty property = new DynamicStringProperty(propName, defaultValue);
addCallback(propertyChangeCallback, property);
在内部就构造了DynamicStringProperty对象,但是这里初始的callback是null,即没有callback,因此需要后续添加
getInstance
factory的getInstance方法中初始化了DynamicPropertySupport
AbstractConfiguration configFromManager = ConfigurationManager.getConfigInstance();
initWithConfigurationSource(configFromManager);
这里可以看到构造apache.commons的类
继续跟踪
initWithConfigurationSource
setDirect(dynamicPropertySupport);
-- setDirect --
DynamicProperty.registerWithDynamicPropertySupport(support);
拿到DynamicPropertySupport调用setDirect方法,然后调用到DynamicProperty中
2 PropertyWrapper - 动态属性包装类
DynamicStringProperty是PropertyWrapper对String类型的包装,在上面构造DynamicStringProperty调用的是父类的构造方法,因此在这里继续往下看:
核心成员变量
# 属性这一概念的抽象
protected final DynamicProperty prop;
# 默认值
protected final V defaultValue;
PropertyWrapper(String propName, V defaultValue) 构造方法
this.prop = DynamicProperty.getInstance(propName);
……
this.prop.addCallback(callback);
……
this.prop.addValidator(new PropertyChangeValidator() {……}
……
prop.updateValue(defaultValue);
可以看到这几步:
- 构造属性抽象
- 添加回调函数
- 添加属性过滤器
- 过滤并配置属性值
3 DynamicProperty - 属性抽象
提供flush清空方法、getValue获取值方法等
核心成员变量和内部类
# 封装了DynamicPropertySupport
private volatile static DynamicPropertySupport dynamicPropertySupportImpl;
# 基础的kv配置:
private String propName;
private String stringValue = null;
# 内部类,缓存配置:
private abstract class CachedValue<T> {
private CachedValue<Boolean> booleanValue
private CachedValue<Integer> integerValue
……
}
可以看到,DynamicProperty提供了一个具有缓存能力的内部类,如果该属性仅被读取一次,则应使用“常规”访问方法。 如果属性值是固定的,请考虑仅将值缓存在变量中。
registerWithDynamicPropertySupport
config.addConfigurationListener(new DynamicPropertyListener());
对封装的DynamicPropertySupport,调用其addConfigurationListener方法,添加了一个默认的Listerner:DynamicPropertyListener
updateProperty
prop.notifyCallbacks();
这里是回调方法触发的入口
notifyCallbacks
for (Runnable r : callbacks) {
try {
r.run();
可以看到,这里同步执行了callbacks里面的方法
4 DynamicPropertyListener
提供了以下默认方法:
addProperty
setProperty
clearProperty
这三个方法是作为属性更新时回调使用的,这三个方法都调用到DynamicProperty#updateProperty中。这里关注一下这三个方法的调用点:ExpandedConfigurationListenerAdapter# configurationChanged
5 ExpandedConfigurationListenerAdapter
还记得这个适配器,在DynamicPropertySupport中,封装了netflix自己的listener,适配成apache.commons的Listener。apache.commons的Listener在使用的时候,都是通过configurationChanged方法触发的,比如ConcurrentMapConfiguration中的fireEvent方法,以及apache.commons中最原始的触发点:EventSource# fireEvent方法
核心成员变量
# 对netflix自己的listener的持有
private PropertyListener expandedListener;
configurationChanged
expandedListener.addProperty(source, name, value, beforeUpdate);
这里直接调用netflix的listener的方法,对应到DynamicPropertyListener的方法,进而触发了用户通过PropertyWrapper# addCallback方法添加的回调函数
6 配置源工具类-ConfigurationUtils
A. 从输入流获取配置信息:loadPropertiesFromInputStream,完成后关闭输入流,返回Properties对象
B. 从文件中获取配置信息:getPropertiesFromFile,返回Properties对象
7 配置管理器-ConfigurationManager
在初始化期间,此类将检查系统属性“archaius.default.configuration.class”和“archaius.default.configuration.factory”。如果设置了前者,它将使用类名通过其默认的 no-arg 构造函数实例化它。如果设置了后者,它将调用其静态方法 getInstance()。
A. 静态代码块,首先做属性判断,觉得使用那种初始化方法。
B. 将属性从指定配置加载到系统范围配置loadPropertiesFromConfiguration
netflix基于动态配置轮询扩展的动态拉取配置能力
除了动态配置广播的能力,netflix还提供了配置动态轮询能力,针对配置文件,可以做周期性拉取操作。
1 动态属性轮询获取能力-DynamicConfiguration
提供动态从数据源获取所有配置值的功能,通过轮询数据源更新配置值。
A. 构造方法
两个入参,PolledConfigurationSource是需要轮询拉取配置的数据源,scheduler用于确定轮询计划。构造方法直接调用startPolling方法,进而调用AbstractPollingScheduler的startPolling方法,再调用initialLoad方法,从而调用source.poll方法,此处为调用方实现的拉取方法。此处为第一次启动拉取,因此poll参数配置均为首次。
2 轮询拉取配置的数据源PolledConfigurationSource
配合DynamicConfiguration的轮询拉取配置使用。
A. poll方法:核心方法,轮询配置源获取最新内容。实现类可以实现poll方法动态轮询更新配置。入参initial如果第一次轮询为true,入参checkPoint如果返回的结果是增量的,则用于确定七点对象,若没有检查的或调用方希望获取完整内容,则为null
B. startPolling方法:启动拉取方法,构造DynamicConfiguration时,自动启动,内部调用initiaLoad方法做首次拉取,并通过getPollingRunnable构造runnable并通过schedule方法定时调用,用户可自行实现schedule方法配置轮询方案。
C. getPollingRunnable内部调用source.poll,用户实现,非首次拉取
netflix复杂的配置类型是如何包装的
1 通过DerivedStringProperty和StringDerivedProperty构造复杂类型的案例
// DerivedStringProperty需要写实现类,实现derive方法进行构造
public class DynamicPersonProperty extends DerivedStringProperty<DynamicPersonProperty.Person> {
public DynamicPersonProperty(String proName , String defalutName){
super(proName , defalutName);
}
//只需要实现这个方法 转换类型
@Override
protected Person derive(String value) {
if (StringUtils.isBlank(value)){
return null;
}else{
Person person = new Person();
String[] split = value.split(",");
person.setName(split[0].split("=")[1]);
person.setAge(split[1].split("=")[1]);
return person;
}
}
@Getter
@Setter
public static class Person{
private String name;
private String age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
}
//测试
public static void main(String[] args) {
DynamicPersonProperty personProperty = new DynamicPersonProperty("student" , null);
System.out.println(personProperty.get());
}
//输出
Person{name='zhangsan', age='30'}
// StringDerivedProperty通过传入function参数实现类型转化
StringDerivedProperty<Person> student = new StringDerivedProperty<Person>("student", null,(str) ->{
Person person = new Person();
String[] split = str.split(",");
person.setName(split[0].split("=")[1]);
person.setAge(split[1].split("=")[1]);
return person;
});
System.out.println(student.getValue());
netflix简单实现案例
1 配置maven依赖
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<version>0.7.7</version>
</dependency>
2 编写动态配置轮询数据源和调度器
public class DynamicConfigurationSource implements PolledConfigurationSource {
@Override
public PollResult poll(boolean initial,Object checkPoint) throws Exception {
Map<String,Object> map = new HashMap<>();
map.put("test",UUID.randomUUID().toString());
return PollResult.createFull(map);
}
}
AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler(2000,2000,false);
3 定义动态配置
DynamicConfiguration configuration = new DynamicConfiguration(source,scheduler);