目 录CONTENT

文章目录

netflix.archaius动态配置解析源码阅读

FatFish1
2024-11-11 / 0 评论 / 0 点赞 / 64 阅读 / 0 字 / 正在检测是否收录...

netflix从数据源获取动态配置的框架

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做一层封装。

DynamicPropertySupport - 动态配置顶层接口

String getString(String propName);
void addConfigurationListener(PropertyListener expandedPropertyListener);

提供了两个接口方法。针对apache.commons,做了一个实现:ConfigurationBackedDynamicPropertySupportImpl,功能和基于apache的ConcurrentMapConfiguration基本一致

构造函数

public ConfigurationBackedDynamicPropertySupportImpl(AbstractConfiguration config)

其实就是基于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

创建监听变化的动态配置的基础框架

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中

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);

可以看到这几步:

  1. 构造属性抽象

  2. 添加回调函数

  3. 添加属性过滤器

  4. 过滤并配置属性值

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里面的方法

DynamicPropertyListener

提供了以下默认方法:

addProperty
setProperty 
clearProperty

这三个方法是作为属性更新时回调使用的,这三个方法都调用到DynamicProperty#updateProperty中。这里关注一下这三个方法的调用点:ExpandedConfigurationListenerAdapter# configurationChanged

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方法添加的回调函数

配置源工具类-ConfigurationUtils

  • 从输入流获取配置信息:loadPropertiesFromInputStream,完成后关闭输入流,返回Properties对象

  • 从文件中获取配置信息:getPropertiesFromFile,返回Properties对象

配置管理器-ConfigurationManager

在初始化期间,此类将检查系统属性“archaius.default.configuration.class”和“archaius.default.configuration.factory”。如果设置了前者,它将使用类名通过其默认的 no-arg 构造函数实例化它。如果设置了后者,它将调用其静态方法 getInstance()。

  • 静态代码块,首先做属性判断,觉得使用那种初始化方法。

  • 将属性从指定配置加载到系统范围配置loadPropertiesFromConfiguration

基于动态配置轮询扩展的动态拉取配置能力

除了动态配置广播的能力,netflix还提供了配置动态轮询能力,针对配置文件,可以做周期性拉取操作。

动态属性轮询获取能力-DynamicConfiguration

提供动态从数据源获取所有配置值的功能,通过轮询数据源更新配置值

构造方法

public DynamicConfiguration(PolledConfigurationSource source, AbstractPollingScheduler scheduler) {
    this();
    startPolling(source, scheduler);
}

两个入参,PolledConfigurationSource是需要轮询拉取配置的数据源,scheduler用于确定轮询计划。构造方法直接调用startPolling方法,进而调用AbstractPollingScheduler#startPolling方法,再调用initialLoad方法,从而调用source#poll方法,此处为调用方实现的拉取方法。此处为第一次启动拉取,因此poll参数配置均为首次

PolledConfigurationSource - 轮询拉取配置的数据源接口

配合DynamicConfiguration的轮询拉取配置使用。

public PollResult poll(boolean initial, Object checkPoint) throws Exception;    

核心方法,轮询配置源获取最新内容。实现类可以实现poll方法动态轮询更新配置。入参initial如果第一次轮询为true,入参checkPoint如果返回的结果是增量的,则用于确定七点对象,若没有检查的或调用方希望获取完整内容,则为null

复杂的配置类型是如何包装的

特殊动态配置分类

  • List系列

    • DynamicListProperty:继承自Property提供transform合from方法做数据转化

    • DynamicStringListProperty:继承自DynamicListProperty

    • DynamicMapProperty:继承自DynamicStringListProperty

    • DynamicStringMapProperty:继承自DynamicMapProperty

    • DynamicSetProperty/DynamicStringSetProperty:逻辑于Map合StringMap类似

  • 复杂类型系列

    • DerivedStringProperty:继承自Property,抽象的derive方法实现了String转化为复杂类型,需调用方自行实现

    • StringDerivedProperty:继承自PropertyWrapper,构造函数中又一个参数是Function decoder,实现String转化复杂类型

通过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简单实现案例

配置maven依赖

<dependency>
    <groupId>com.netflix.archaius</groupId>
    <artifactId>archaius-core</artifactId>
    <version>0.7.7</version>
</dependency>

编写动态配置轮询数据源和调度器

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);

定义动态配置

DynamicConfiguration configuration = new DynamicConfiguration(source,scheduler);



0

评论区