[享學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。它提供了以下功能:

  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>

依賴項:

一旦我們添加了所需的依賴項,我們就能夠訪問框架管理的屬性,並且開箱即用。


快速示例

下面以一個最簡單示例,開啟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自帶的熱載入機制貌似更加優秀些~)