前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Netflix] 九、Archaius配置管理库:初体验及基础API详解

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

作者头像
YourBatman
发布2020-03-18 19:30:26
1.6K0
发布2020-03-18 19:30:26
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

要相信:你遇到的问题,肯定不止你一个人遇到过。 代码下载地址:https://github.com/f641385712/netflix-learning

目录
  • 前言
  • 正文
    • Netflix Archaius
      • 快速示例
    • 基础API
      • PolledConfigurationSource
        • URLConfigurationSource
    • AbstractPollingScheduler
      • FixedDelayPollingScheduler
      • 使用示例

  • 总结
    • 声明

前言

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

代码语言:javascript
复制
<dependency>
    <groupId>com.netflix.archaius</groupId>
    <artifactId>archaius-core</artifactId>
    <version>0.7.7</version>
</dependency>

依赖项:

在这里插入图片描述
在这里插入图片描述

一旦我们添加了所需的依赖项,我们就能够访问框架管理的属性,并且开箱即用。


快速示例

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

代码语言:javascript
复制
@Test
public void fun1(){
    DynamicStringProperty nameProperty = DynamicPropertyFactory.getInstance().getStringProperty("name", "defaultName");
    System.out.println(nameProperty.get());
}

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

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

代码语言:javascript
复制
name=YourBatman

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

代码语言:javascript
复制
YourBatman

基础API

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

PolledConfigurationSource

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

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

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


URLConfigurationSource

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

代码语言:javascript
复制
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()中提供特定的调度逻辑。

代码语言:javascript
复制
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:

代码语言:javascript
复制
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来实现轮询。

代码语言:javascript
复制
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的,不会造成乱调用的情况。


使用示例
代码语言:javascript
复制
@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);
    }
}

运行程序,控制台输出:

代码语言:javascript
复制
name=YourBatman
name=YourBatman-changed
name=YourBatman

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

代码语言:javascript
复制
@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);
    }
}

控制台情况:

代码语言:javascript
复制
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自带的热加载机制貌似更加优秀些~)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 前言
  • 正文
    • Netflix Archaius
      • 快速示例
    • 基础API
      • PolledConfigurationSource
    • AbstractPollingScheduler
    • 总结
    相关产品与服务
    负载均衡
    负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档