[享学Netflix] 九、Netflix Archaius配置管理库:初体验及基础API详解

要相信:你遇到的问题,肯定不止你一个人遇到过。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

Netflix是一家互联网流媒体播放商,是美国视频巨头,随着Netflix转型为一家云计算公司,它也开始积极参与开源项目,并且提供了众多好用的开源产品,比如耳熟能详的就有:

  • Netflix/zuul:网关
  • Netflix/hystrix:断路器
  • Netflix/ribbon:客户端负载均衡器
  • Netflix/archaius:配置管理
  • Netflix/feign:Http客户端
  • Netflix/eureka:配置中心
  • Netflix/conductor:服务编排、任务调度
  • Netflix/hollow:处理内存数据集的Java库

由于Spring Cloud主流技术栈都是构建在Netflix OSS(Open Source)基础上,因此本专栏主要学习些Netflix OSS组件的基础知识,为云源生相关专题做好知识储备。
另需说明:本系列讲述的是源生的Netflix OSS套件,并不是已经集成好的spring-cloud-starter-netflix-archaius

本篇作为Netflix OSS系列第一篇文章,那就先介绍Archaius,因为他是基础。

说明:Netflix Archaius是对Apache Commons Configuration库的扩展,而关于Commons Configuration的介绍前面花了8篇文章详细描述,请移步了解


正文

源码地址:https://github.com/Netflix/archaius
名称由来:这个项目的代号来自一种濒临灭绝的变色龙Archaius。因为变色龙以根据环境和环境改变颜色(一种属性)而闻名。这个和该项目的愿望是契合的:使用动态属性更改来影响基于特定上下文的运行时行为。

总之:Netflix Archaius是一个功能强大的配置管理库。它是一个可用于从许多不同来源收集配置属性的框架,Archaius包含一组由Netflix使用的配置管理api。它提供了以下功能:

  1. 动态、类型的属性
  2. 高吞吐量和线程安全的配置操作
  3. 允许获取Configuration Source配置源的属性更改的轮询框架
  4. 配置改变时的回调机制Callback
  5. 一个JMX MBean,可以通过JConsole访问它来检查和调用属性上的操作(查询和修改等)
  6. 组合配置(复合配置,和Spring的属性源很像)
  7. 动态配置

Archaius允许属性在运行时动态更改,使系统无需重新启动应用程序即可获得这些变化。


Netflix Archaius

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

依赖项:

扫描二维码关注公众号,回复: 9382463 查看本文章

在这里插入图片描述
一旦我们添加了所需的依赖项,我们就能够访问框架管理的属性,并且开箱即用。


快速示例

下面以一个最简单示例,开启Netflix Archaius的使用。

@Test
public void fun1(){
    DynamicStringProperty nameProperty = DynamicPropertyFactory.getInstance().getStringProperty("name", "defaultName");
    System.out.println(nameProperty.get());
}

默认情况下,它动态管理应用程序类路径中名为config.properties的文件中定义的所有属性。

因此我在classpath下新建一个文件config.properties

name=YourBatman

运行以上程序,控制台输出:

YourBatman

基础API

为了更好的理解Netflix Archaius,有些基础API你不得不知。

PolledConfigurationSource

配置源的定义接口,通过轮询对配置进行动态更改。

public interface PolledConfigurationSource {
	// 轮询配置源以获得最新内容。
	// initial:如果是首次轮询,填true
	// checkPoint:检查点。如果返回的内容是底层的,确定起始点。如果木有填写null
	// PoolResult:返回配置的内容。可以是full的,也可能是增量的
    public PollResult poll(boolean initial, Object checkPoint) throws Exception;    
}

它有两个实现类:JDBCConfigurationSourceURLConfigurationSource。前者基于数据库使用DataSource去查询实现(每次返回全量结果),后者基于一组url的轮询配置源,本文只关注后者。


URLConfigurationSource

基于一组url的轮询配置源。对于每一次轮询,它总是返回所有文件中定义的属性的完整联合。如果一个属性定在多个文件里了,那么后者覆盖前者。

public class URLConfigurationSource implements PolledConfigurationSource {
	private final URL[] configUrls;

	// 你还可以通过系统属性,外部化定义URLS
	public static final String CONFIG_URL = "archaius.configurationSource.additionalUrls";
	// 默认会在类路径下**轮询此文件名**
	// 此文件名可通过下面的系统属性DEFAULT_CONFIG_FILE_PROPERTY更改
	public static final String DEFAULT_CONFIG_FILE_NAME = "config.properties";
	public static final String DEFAULT_CONFIG_FILE_PROPERTY = "archaius.configurationSource.defaultFileName";


	public static final String DEFAULT_CONFIG_FILE_FROM_CLASSPATH = System.getProperty(DEFAULT_CONFIG_FILE_PROPERTY, DEFAULT_CONFIG_FILE_NAME);

	// 字符串通过new URL转换为URL对象
    public URLConfigurationSource(String... urls) {
       configUrls = createUrls(urls);
    }
    public URLConfigurationSource(URL... urls) {
        configUrls = urls;
    }
    
    // 以上是自己指定URL,那么若你是用空构造器
	// 1、从classpath里去找名为config.properties的文件
	// 2、在找你自己自定义配置的CONFIG_URL
	// 3、注意:相同key后者覆盖前者哦。所以config.properties优先级最低
	public URLConfigurationSource() { ... }

	// 返回结果很简单:把每个文件属性都加载进来
	// 合并后,完整输出PollResult.createFull(map);
    @Override
    public PollResult poll(boolean initial, Object checkPoint) throws IOException {    
        Map<String, Object> map = new HashMap<String, Object>();
        for (URL url: configUrls) {
           ...
        }
        return PollResult.createFull(map);
    }
}

PollResultWatchedUpdateResult的子类,里面维护着这几个属性:protected final Map<String, Object> complete, added, changed, deleted;,清晰的记录着每次的各种变化~

PolledConfigurationSource的主要作用是提供配置,并且提供对轮询的能力,但是什么时候轮询,频次多少,就看poll()方法是如何被调用的,这就引申到下面的Scheduled喽。


AbstractPollingScheduler

此抽象类负责调度配置的定期轮询并应用将结果轮询到配置。子类应该在schedule(Runnable)stop()中提供特定的调度逻辑。

public abstract class AbstractPollingScheduler {

	// 是否忽略配置源里的删除
	// true:忽略。也就是源头删除了,但是内存里不做变更
	// 一般不建议改为true,保持false即可
	private volatile boolean ignoreDeletesFromSource;
	// 轮询时监听器,也就是执行poll时候的监听器
	private List<PollListener> listeners = new CopyOnWriteArrayList<PollListener>();
	...
	//DynamicPropertyUpdater 的作用是:几个Configuration和WatchedUpdateResult
	// 看看哪些需要新增、哪些需要删除、哪些需要update等等
	private DynamicPropertyUpdater propertyUpdater = new DynamicPropertyUpdater();
	
	// 唯一构造器
    public AbstractPollingScheduler() {
        this.ignoreDeletesFromSource = false;
    }

	// 初始化源,并且将其结果放在Config里面(应用)
    protected synchronized void initialLoad(final PolledConfigurationSource source, final Configuration config) {      
        PollResult result = null;
        try {

			// 请注意:这个EventType是Netflix自己的哟,和Apache事件无关
			// 触发所有的PollListener执行
            fireEvent(EventType.POLL_BEGIN, null, null);
            result = source.poll(true, null); // 执行初始化
            checkPoint = result.getCheckPoint();

				// 把result的结果,应用到config里面去
				// 借助propertyUpdater来完成
                populateProperties(result, config);
			
			// 发送成功事件。数据有result
            fireEvent(EventType.POLL_SUCCESS, result, null);
        } catch (Exception e) {
        	// 发送失败事件,没有数据但是有异常
            fireEvent(EventType.POLL_FAILURE, null, e);
            throw new RuntimeException("Unable to load Properties source from " + source, e);
        }
    }

	// 方法执行逻辑完全同上,只不过这里是以Runnable的形式返回
	// 延迟执行
	protected Runnable getPollingRunnable(final PolledConfigurationSource source, final Configuration config) { ... }
	... // 省略属性访问、注册事件等方法

	// 一般只有结果是递增的时候才有用
    protected Object getNextCheckPoint(Object lastCheckpoint) {
        return lastCheckpoint;
    }
}

以上是AbstractPollingScheduler提供的一些“工具方法”,也是最为核心的方法。下面应该看看它的public API:

AbstractPollingScheduler:
	
	// 开始,启动轮询
	// 请注意:因为要启动schedule,所以这里使用的Runable模式
    public void startPolling(final PolledConfigurationSource source, final Configuration config) {
        initialLoad(source, config);
        Runnable r = getPollingRunnable(source, config);
        schedule(r);
    }

	// 教你启动了,但停止需子类自行实现
	protected abstract void schedule(Runnable pollingRunnable);
	public abstract void stop();

轮询是通过schedule去实现,至于schedule()如何实现,是用JDK的,还是用第三方框架的,这个是子类来定的事。


FixedDelayPollingScheduler

这是它的唯一内置子类。它使用的是java.util.concurrent.ScheduledExecutorService来实现轮询。

public class FixedDelayPollingScheduler extends AbstractPollingScheduler {
    private ScheduledExecutorService executor;
    private int initialDelayMillis = 30000; // 初始化后30s启动轮询
    private int delayMillis = 60000; // 默认1分钟轮询一次

	// 这两个值均可通过系统属性方式设置,调整
	// 当然你也可以通过构造器显示指定
	// 构造器显示指定的优先级大于系统属性哦~~~~
    public static final String INITIAL_DELAY_PROPERTY = "archaius.fixedDelayPollingScheduler.initialDelayMills";
    public static final String DELAY_PROPERTY = "archaius.fixedDelayPollingScheduler.delayMills";

	... // 省略构造器为属性赋值

	// 请注意:每调用一次schedule()方法,均生成了一个新的executor实例哟
	// 所以schedule()不要同一实例调用多次啦
    @Override
    protected synchronized void schedule(Runnable runnable) {
        executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "pollingConfigurationSource");
                t.setDaemon(true); // 守护线程执行Runnable
                return t;
            }
        });
        // Timer定时器方式,周期性执行任务
        executor.scheduleWithFixedDelay(runnable, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS);        
    }
        
    @Override
    public void stop() {
        if (executor != null) {
            executor.shutdown();
            executor = null;
        }
    }
}

schedule()方法为同一Runnable任务可以执行多次,但还好该方法不是public的,不会造成乱调用的情况。


使用示例
@Test
public void fun3() throws InterruptedException {
    PropertiesConfiguration config = new PropertiesConfiguration();

    // 开启轮询,只有文件内容有变化就实时同步
    FixedDelayPollingScheduler scheduler = new FixedDelayPollingScheduler(3000, 5000, false);
    scheduler.startPolling(new URLConfigurationSource(), config);

    while (true) {
        ConfigurationUtils.dump(config, System.out);
        System.out.println();
        TimeUnit.SECONDS.sleep(10);
    }
}

运行程序,控制台输出:

name=YourBatman
name=YourBatman-changed
name=YourBatman

加上监听器,打印线程名称,来个增强版:

@Test
public void fun3() throws InterruptedException {
    PropertiesConfiguration config = new PropertiesConfiguration();

    // 开启轮询,只有文件内容有变化就实时同步
    FixedDelayPollingScheduler scheduler = new FixedDelayPollingScheduler(3000, 5000, false);
    scheduler.startPolling(new URLConfigurationSource(), config);
    scheduler.addPollListener((eventType, result, exception) -> {
        if (eventType == PollListener.EventType.POLL_SUCCESS) {
            System.out.println("线程名称:" + Thread.currentThread().getName());

            System.out.println("新增属性们:" + result.getAdded());
            System.out.println("删除属性们:" + result.getDeleted());
            System.out.println("修改属性们:" + result.getChanged());
            System.out.println("完成属性们:" + result.getComplete());
        }
    });

    while (true) {
        ConfigurationUtils.dump(config, System.out);
        System.out.println();
        TimeUnit.SECONDS.sleep(10);
    }
}

控制台情况:

name=YourBatman
线程名称:pollingConfigurationSource
新增属性们:null
删除属性们:null
修改属性们:null
完成属性们:{name=YourBatman}
线程名称:pollingConfigurationSource
新增属性们:null
删除属性们:null
修改属性们:null
完成属性们:{name=YourBatman}

// 从此处发生了改变
线程名称:pollingConfigurationSource
新增属性们:null
删除属性们:null
修改属性们:null
完成属性们:{name=YourBatman-changed, age=18}
name=YourBatman-changed
age=18
...

由此可见:每次轮询都会执行源的poll方法,所以都会发送相应事件哦。
另外,关于protected final Map<String, Object> complete, added, changed, deleted;这些属性,是需要你自己通过构造WatchedUpdateResult时区分出来的,如果你仅仅是PollResult.createFull(Map<String, Object> complete),那它仅仅只有设置complete值哦。

说明:PollResult#createIncremental()是区分出所有(哪些新增、哪些删除),不过实现起来稍微麻烦点,需要你自行实现~


总结

关于Netflix Archaius的初体验以及基础API的解释就到这了,了解了内置支持的配置源以及轮询机制,并且辅以案例进行了讲解,应该知道Netflix Archaius的动态配置是如何实现的了。

对于Netflix Archaius实现配置动态化和Apache Commons Configuration实现热加载,前者很明显简单很多,并且设计上更加的产品化一点,推荐使用。

说明:Netflix Archaius的动态配置属性并不依赖于Apache Commons Configuration的热加载机制,而是自己实现的轮询策略。(当然喽,这很可能和它依赖的是Commons Configuration1.x有关,若是2.x使用Commons Configuration自带的热加载机制貌似更加优秀些~)

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了309 篇原创文章 · 获赞 467 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104348988