[享學Netflix] 九、Archaius配置管理庫:初體驗及基礎API詳解
- 2020 年 3 月 18 日
- 筆記
要相信:你遇到的問題,肯定不止你一個人遇到過。 程式碼下載地址: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。它提供了以下功能:
- 動態、類型的屬性
- 高吞吐量和執行緒安全的配置操作
- 允許獲取
Configuration Source
配置源的屬性更改的輪詢框架 - 配置改變時的回調機制Callback
- 一個
JMX MBean
,可以通過JConsole
訪問它來檢查和調用屬性上的操作(查詢和修改等) - 組合配置(複合配置,和Spring的屬性源很像)
- 動態配置
Archaius
允許屬性在運行時動態更改,使系統無需重新啟動應用程式即可獲得這些變化。
Netflix Archaius
<dependency> <groupId>com.netflix.archaius</groupId> <artifactId>archaius-core</artifactId> <version>0.7.7</version> </dependency>
依賴項:

一旦我們添加了所需的依賴項,我們就能夠訪問框架管理的屬性,並且開箱即用。
快速示例
下面以一個最簡單示例,開啟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; }
它有兩個實現類:JDBCConfigurationSource
和URLConfigurationSource
。前者基於資料庫使用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); } }
PollResult
是WatchedUpdateResult
的子類,裡面維護著這幾個屬性: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
自帶的熱載入機制貌似更加優秀些~)