【微服務技術專題】Netflix動態化配置服務-微服務配置元件變色龍Archaius

李浩宇Alex發表於2021-08-27

前提介紹

  • 如果要設計開發一套微服務基礎架構,引數化配置是一個非常重要的點,而Netflix也開源了一個叫變色龍Archaius的配置中心客戶端,而且Archaius可以說是比其他客戶端具備更多生產級特性,也更靈活。

  • 在NetflixOSS微服務技術棧中,幾乎所有的其它元件(例如Zuul, Hystrix, Eureka, Ribbon等)都依賴於Archaius,可以說理解Archaius是理解和使用Netflix其它微服務元件的基礎。


Archaius是什麼

Netflix Archaius是一個配置管理庫,其重點是來自多個配置儲存的動態屬性。它包括一組用於Netflix的Java配置管理API。它主要實現為Apache Commons Configuration庫的擴充套件。提供的主要功能有:

注意,Netflix只是開源了其配置中心的客戶端部分(也就是Archaius),沒有開源配套的伺服器端。Archaius其實是配置源實現無關的,可以對接各種配置中心作為資料來源,本文後面會介紹Archaius如何和Apollo配置中心進行整合。

Archaius專案的由來

  • 在微服務環境下,配置常常需要根據不同的上下文環境進行調整,或者說配置應該是多維度的。例如在Netflix,上下文維度包括環境(開發、測試和生產)。

  • Netflix希望能夠根據釋出的環境,甚至請求的上下文,動態地調整服務的配置,讓Netflix的整個系統的行為和邏輯變得動態可調配,以適應網際網路應用快速多變的需求。為此,Netflix平臺團隊開發了配置中心產品,團隊將這個產品形象地稱為變色龍Archaius,因為變色龍這種動物能夠根據自己所處的環境動態調整身體的顏色。

Archaius在Netflix的用例場景

  1. 根據請求上下文開啟或關閉某項功能。
  2. 某個頁面預設顯示10個商品,在某些情況下,可以通過Archaius調整配置,只顯示5個商品。
  3. 動態調整Hystrix熔斷器的行為。
  4. 調整服務呼叫客戶端的連線和請求超時引數。
  5. 如果某個線上服務產生出錯告警,可以動態調整日誌輸出級別(粒度可以細到包或者元件級別),這樣可以通過詳細日誌排查問題。問題定位以後,再將日誌輸出級別恢復到預設級別。
  6. 對於多區域或者多國家部署的應用,通過動態配置,可以根據不同區域和國家開啟不同的功能。
  7. 可以根據使用者的實際訪問模式動態調整一些基礎中介軟體的配置,例如快取的存活時間TTL(Time To Live)。
  8. 資料庫訪問客戶端的連線池配置,可以對不同服務配不同的值。例如,一個請求頻率RPS(Request Per Second)小的服務,可以配置較小的連線數,而一個請求頻率大的服務,可以配置較大的連線數。
  9. 執行期配置的變更可以在不同維度生效,例如叢集中的單個例項維度,多區域部署下的某個區域維度,某個服務棧維度,或者某個應用叢集維度。
  10. 功能開關(Feature Flag)釋出,有些功能雖然上線,但是並不馬上啟用,而是通過配置開關動態啟用,這樣可以根據情況靈活開啟或者關閉某項線上功能。
  11. 金絲雀釋出(Canary Release),新功能上線時,讓新老叢集同時並存一段時間,通過配置將到老叢集的流量逐步動態調整到新叢集,如果監控顯示無異常,則完成新叢集的上線,如異常,則快速切回老叢集。

Archaius的技術基礎

  • archaius是netflix開源的動態屬性配置框架,基於apache commons configuration, 提供在執行時獲取配置值的功能。

  • Archaius的核心是可以容納一個或多個配置的複合配置的概念。每個配置都可以從諸如JDBC、REST介面、xxx.properties檔案等配置源中獲取。可以選擇在執行時對配置源進行輪詢以進行動態更改,

  • 屬性的最終值取決於包含該屬性的最頂層配置(因為是複合配置)。即,如果一個屬性存在於多個配置中,則應用程式看到的實際值將是配置層次結構中最頂層插槽中的值,當然這種層次結構是可以配置的。

Archaius的架構設計

Archaius實際上是對Apache Common Configuration Library的一個封裝和擴充套件,提供了一組基於Java的配置API,主要的特性包括:

  • 配置可動態調整:動態、型別屬性
  • 配置支援型別(Int, Long, Boolean等)。
  • 高效能和執行緒安全:高吞吐量和執行緒安全的配置操作
  • 提供一個拉(pulling)配置的框架,可以從配置源動態拉取變更的配置。(一個輪詢框架,允許使用者獲取對配置源的屬性更改)
  • 支援回撥(callback)機制,在配置變更時自動呼叫。
  • 支援JMX MBean,可以通過JConsole檢視配置和修改配置。

對於願意使用基於約定的屬性檔案位置的應用程式(以及大多數web應用程式),提供開箱即用的複合配置(這是強大功能之一),對於符合配置官網給了一副示例圖如下:

Achaius的核心是一個稱為組合配置(Composite Configuration)的概念,簡單可以理解為一個分層級的配置,層級有優先順序,高優先順序的層級的配置會覆蓋低優先順序的配置。每一個層級可以從某個配置源獲取配置,例如本地配置檔案,JDBC資料來源,遠端REST API等。配置源還可以在執行時動態拉取變更,例如在上圖中,持久化資料庫配置(Persisted DB Configuration)是指將配置存在關聯式資料庫中,相應的配置源會定期從資料庫拉取變更)。配置的最終值由頂級配置決定,例如,如果多個層級都含有某個配置項,那麼應用最終見到的值是配置層級中最頂層的值。配置分層的順序是可以調整的。

通過archaius獲取配置值,有兩種方式:
  • 一種是通過ConfigurationManager獲取到配置中心例項,然後通過propName獲取配置值,通過ConfigurationManager獲取配置
  • 另外一種方式是通過DynamicPropertyFactory,獲取配置項的DynamicProperty wrapper。

Archaius的實現原理

Archaius是什麼?

Archaius提供了動態修改配置的值的功能,在修改配置後,不需要重啟應用服務。其核心思想就是輪詢配置源,每一次迭代,檢測配置是否更改,有更改重新更新配置。

底層archaius提供實現了Apache-common-configuration的AbstractConfiguration的具體實現。

  • ConcurrentMapConfiguration提供將配置項配置值放在ConcurrentHashMap中維護的功能。

  • DynamicConfiguration提供動態從資料來源獲取所有配置值的功能,通過輪詢資料來源更新配置值。


  • DynamicWatchedConfiguration也是提供動態更新配置的功能,與DynamicConfiguration不同的是,配置更新是資料來源有變化時觸發的。

  • DynamicConfiguration是pull方式,DynamicWatchedConfiguration是push方式。

ConcurrentCompositeConfiguration使用了組合模式,組合不同的AbstractConfiguration實現。對於有多個配置源的配置中心,可以使用ConcurrentCompositeConfiguration。對於同一個配置項,多個配置源都有配置值的時候,取第一個匹配到的配置源的資料。

DynamicPropertyFactory是怎麼執行的?

  • DynamicPropertyFactory持有AbstractConfiguration例項。

  • 建立DynamicProperty物件時,DynamicProperty物件會獲取DynamicPropertyFactory持有的AbstractConfiguration例項。

  • DynamicProperty物件會向AbstractConfiguration例項註冊DynamicPropertyListener, 當AbstractConfiguration有增刪改查變化時,會通知到當前的DynamicProperty物件。

當建立的DynamicProperty例項數量比較大的時候,這裡可能有效能問題。每建立一個任何一個DynamicProperty,都會增加一個listener,同時,任何一個配置項發生變化,都會觸發listener。

可能是考慮到生產環境中不會有那麼多的配置項變更吧。像zk-config那種對應配置項變更才觸發watcher要好一點。

AbstractConfiguration是怎麼注入的?

archaius僅允許一個AbstractConfiguration的實現類。如果有多個配置源,可以使用上面提到的ConcurrentCompositeConfiguration將不同的AbstractConfiguration組合起來。

有以下幾種方式注入AbstractConfiguration。
  1. 配置archaius.default.configuration.class 指定AbstractConfiguration實現類

  2. 配置 archaius.default.configuration.factory 指定AbstractConfiguration例項工廠方法類,工廠方法類需要實現getInstance方法返回AbstractConfiguration例項

  3. 呼叫 ConfigurationManager 的 install(AbstractConfiguration config)  方法

  4. 呼叫DynamicPropertyFactory 的 initWithConfigurationSource(AbstractConfiguration config) 方法

以上方法是互斥的,只能使用其中的一種,一種生效後,其他的就不能呼叫了。

一個簡單的例子:
Maven依賴配置
<dependency>
    <groupId>com.netflix.archaius</groupId>
    <artifactId>archaius-core</artifactId>
    <version>0.7.7</version>
</dependency>
獲取配置源
/**
 * Created by longfei on 17/1/19.
 */
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);
定義動態配置
DynamicConfiguration configuration = new DynamicConfiguration(source,scheduler);
簡單單元測試
@org.testng.annotations.Test
    public void testArchaius() throws Exception {
        PolledConfigurationSource source = new DynamicConfigurationSource();
        AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler(2000,2000,false);
        DynamicConfiguration configuration = new DynamicConfiguration(source,scheduler);
        ConfigurationManager.install(configuration);
        final DynamicStringProperty stringProperty = DynamicPropertyFactory.getInstance().getStringProperty("test","nodata");
        Helpers.subscribePrint(Observable.interval(1,TimeUnit.SECONDS).take(20).doOnNext(new Action1<Long>() {
                    @Override
                    public void call(Long aLong) {
                        System.out.println(stringProperty.get());
                    }
                }),"test");
        TimeUnit.MINUTES.sleep(1);
    }

實現

啟動輪詢任務
public synchronized void startPolling(PolledConfigurationSource source, AbstractPollingScheduler scheduler) {
    this.scheduler = scheduler;
    this.source = source;
    init(source, scheduler);
    scheduler.startPolling(source, this);
}
輪詢的Runnable和初始化:實現是一致的
PollResult result = null;
    try {
       result = source.poll(false,getNextCheckPoint(checkPoint));
       checkPoint = result.getCheckPoint();
       fireEvent(EventType.POLL_SUCCESS, result, null);
       } catch (Throwable e) {
       log.error("Error getting result from polling source", e);
       fireEvent(EventType.POLL_FAILURE, null, e);
       return;
       }
       try {
          populateProperties(result, config);
       } catch (Throwable e) {
           log.error("Error occured applying properties", e);
      }

注意到,會呼叫source.poll方法,即PolledConfigurationSource的polled,我們實現的資料來源介面,可以自定義資料來源(jdbc,檔案,scm等)

總結

在深入理解Archaius過程中,有一個繞不開的“障礙”便是Apache Commons Configuration,由於前者強依賴於後者進行配置管理。正所謂你對Apache Commons Configuration有多瞭解,決定了你對Netflix Archaius的認識有多深。

資料參考

相關文章