netflix.archaius动态配置解析

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

1 从apache.configuration到netfilx

https://ucc.alicdn.com/pic/developer-ecology/6w4x54kwa7p4m_de84a8d1a25f4eb4977afdc6130f18c8.png

apache.commons.configuration包是apache提供的一套解析properties文件的能力。其中提供了一个抽象类AbstractConfiguration。其中有很多实现,例如:

  1. PropertiesConfiguration:commons包提供的一个经典的配置加载器。实现AbstractFileConfiguration,提供了从文件中加载配置的能力
  1. ConcurrentMapConfiguration:netflix提供的实现,内部使用了一个ConcurrentHashMap,用于并发场景线程安全和吞吐量
    1. 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);

可以看到这几步:

  1. 构造属性抽象
  2. 添加回调函数
  3. 添加属性过滤器
  4. 过滤并配置属性值

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>