Apollo 3 定時/長輪詢拉取配置的設計


Apollo 基礎模型

前言

如上圖所示,Apollo portal 更新配置后,進行輪詢的客戶端獲取更新通知,然后再調用接口獲取最新配置。不僅僅只有輪詢,還有定時更新(默認 5 分鍾一次)。目的就是讓客戶端能夠穩定的獲取到最新的配置。

一起來看看他的設計。

核心代碼

具體的類是 RemoteConfigRepository,每一個 Config —— 也就是 namespace 都有一個 RemoteConfigRepository 對象,表示這個 Config 的遠程配置倉庫,可以利用這個倉庫請求遠程服務,得到配置。

RemoteConfigRepository 的構造方法需要一個 namespace 字符串,表明這個 Repository 所屬的 Config 名稱。

下面是該類的構造方法。

public RemoteConfigRepository(String namespace) {
    m_namespace = namespace;// Config 名稱
    m_configCache = new AtomicReference<>(); //  Config 引用
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);// 單例的 config 配置,存放 application.properties
    m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);// HTTP 工具
    m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);// 遠程服務 URL 更新類
    remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);// 長輪詢服務
    m_longPollServiceDto = new AtomicReference<>();// 長輪詢發現的當前配置發生變化的服務
    m_remoteMessages = new AtomicReference<>();
    m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());// 限流器
    m_configNeedForceRefresh = new AtomicBoolean(true);// 是否強制刷新
    m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),//1
        m_configUtil.getOnErrorRetryInterval() * 8);// 1 * 8;失敗定時重試策略: 最小一秒,最大 8 秒.
    gson = new Gson();// json 序列化
    this.trySync(); // 第一次同步
    this.schedulePeriodicRefresh();// 定時刷新
    this.scheduleLongPollingRefresh();// 長輪詢刷新
}

可以看到,在構造方法中,就執行了 3 個本地方法,其中就包括定時刷新和長輪詢刷新。這兩個功能在 apollo 的 github 文檔中也有介紹:

1.客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。
2.客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
3.這是一個fallback機制,為了防止推送機制失效導致配置不更新。
4.客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified。
5.定時頻率默認為每5分鍾拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位為分鍾。

所以,長連接是更新配置的主要手段,然后用定時任務輔助長連接,防止長連接失敗。

那就看看長連接和定時任務的具體代碼。

定時任務

定時任務主要由一個單 core 的線程池維護這定時任務。

static {
    // 定時任務,單個 core. 后台線程
    m_executorService = Executors.newScheduledThreadPool(1,
        ApolloThreadFactory.create("RemoteConfigRepository", true));
}

private void schedulePeriodicRefresh() {
    // 默認 5 分鍾同步一次.
    m_executorService.scheduleAtFixedRate(
        new Runnable() {
          @Override
          public void run() {
            trySync();
          }
        }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),// 5
        m_configUtil.getRefreshIntervalTimeUnit());//單位:分鍾
}

具體就是每 5 分鍾執行 sync 方法。我簡化了一下 sync 方法,一起看看:

protected synchronized void sync() {
  ApolloConfig previous = m_configCache.get();
  // 加載遠程配置
  ApolloConfig current = loadApolloConfig();

  //reference equals means HTTP 304
  if (previous != current) {
    m_configCache.set(current);
    // 觸發監聽器
    this.fireRepositoryChange(m_namespace, this.getConfig());
  }
}

首先,拿到上一個 config 對象的引用,然后,加載遠程配置,判斷是否相等,如果不相等,更新引用緩存,觸發監聽器。

可以看出,關鍵是加載遠程配置和觸發監聽器,這兩個操作。

loadApolloConfig 方法主要邏輯就是通過 HTTP 請求從 configService 服務里獲取到配置。大概步驟如下:

  1. 首先限流。獲取服務列表。然后根據是否有更新通知,決定此次重試幾次,如果有更新,重試2次,反之一次。
  2. 優先請求通知自己的 configService,如果失敗了,就要進行休息,休息策略要看是否得到更新通知了,如果是,就休息一秒,否則按照 SchedulePolicy 策略來。
  3. 拿到數據后,重置強制刷新狀態和失敗休息狀態,返回配置。

觸發監聽器步驟:

  1. 循環遠程倉庫的監聽器,調用他們的 onRepositoryChange 方法。其實就是 Config。
  2. 然后,更新 Config 內部的引用,循環向線程池提交任務—— 執行 Config 監聽器的 onChange 方法。

好,到這里,定時任務就算處理完了,總之就是調用 sync 方法,請求遠程 configServer 服務,得到結果后,更新 Config 對象里的配置,並通知監聽器。

再來說說長輪詢。

長連接 / 長輪詢

長輪詢實際上就是在一個類似死循環里,不停請求 ConfigServer 的配置變化通知接口 notifications/v2,如果配置有變更,就會返回變更信息,然后向定時任務線程池提交一個任務,任務內容是執行 sync 方法。

在請求 ConfigServer 的時候,ConfigServer 使用了 Servlet 3 的異步特性,將 hold 住連接 30 秒,等到有通知就立刻返回,這樣能夠實現一個基於 HTTP 的長連接。

關於為什么使用 HTTP 長連接,初次接觸 Apollo 的人都會疑惑,為什么使用這種方式,而不是"那種"方式?

下面是作者宋順的回復:

總結一下:

  1. 為什么不使用消息系統?太復雜,殺雞用牛刀。
  2. 為什么不用 TCP 長連接?對網絡環境要求高,容易推送失敗。且有雙寫問題。
  3. 為什么使用 HTTP 長輪詢?性能足夠,結合 Servlet3 的異步特性,能夠維持萬級連接(一個客戶端只有一個長連接)。直接使用 Servlet 的 HTTP 協議,比單獨用 TCP 連接方便。HTTP 請求/響應模式,保證了不會出現雙寫的情況。最主要還是簡單,性能暫時不是瓶頸

總結

本文沒有貼很多的代碼。因為不是一篇源碼分析的文章。

總之,Apollo 的更新配置設計就是通過定時輪詢和長輪詢進行組合而來。

定時輪詢負責調用獲取配置接口,長輪詢負責調用配置更新通知接口,長輪詢得到結果后,將提交一個任務到定時輪詢線程池里,執行同步操作——也就是調用獲取配置接口。

為什么使用 HTTP 長輪詢? 簡單!簡單!簡單!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM