apollo配置中心之-如何啓動源碼及Config Service 通知配置變化源碼解析

參考:appollo官方

文章目錄

如何啓動源碼

下載源碼

1、源碼結構

在這裏插入圖片描述

2、初始化庫

在這裏插入圖片描述

3、啓動apollo-assembly

application啓動配置如下:
在這裏插入圖片描述

VM options:

-Denv=dev
-Dspring.profiles.active=github
-Deureka.service.url=http://localhost:8080/eureka/
-Dspring.datasource.url=jdbc:mysql://localhost:3309/ApolloConfigDB?characterEncoding=utf8
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/logs/apollo-assembly.log

Program arguments:

–configservice --adminservice

這些配置其實可以參考官方的項目apollo-build-scripts項目

這個項目有一個deom.sh腳本如下:不需要完全看懂所有意思,只需看重點如下:

# meta server url定義的一些變量
config_server_url=http://localhost:8080
admin_server_url=http://localhost:8090
eureka_service_url=$config_server_url/eureka/
portal_url=http://localhost:8070

# JAVA OPTS BASE_JAVA_OPTS:基礎變量
BASE_JAVA_OPTS="-Denv=dev"
CLIENT_JAVA_OPTS="$BASE_JAVA_OPTS -Dapollo.meta=$config_server_url"
#config service和admin service所需的VM options配置參數
SERVER_JAVA_OPTS="$BASE_JAVA_OPTS -Dspring.profiles.active=github -Deureka.service.url=$eureka_service_url"
#portal service所需的VM options配置參數
PORTAL_JAVA_OPTS="$BASE_JAVA_OPTS -Ddev_meta=$config_server_url -Dspring.profiles.active=github,auth -Deureka.client.enabled=false -Dhibernate.query.plan_cache_max_size=192"
#啓動config service和admin service時所帶的Program arguments參數
$SERVICE_JAR start --configservice --adminservice

啓動報錯

注意apollo-assembly這個項目是將config 和admin一起啓動的, 並且是先啓動config 再啓動admin。因爲config這個服務中整合了eureka 服務端,所以要先把eureka服務啓動,然後config 和admin再註冊到eureka。

注:在啓動apollo-configservice的過程中會在日誌中輸出eureka註冊失敗的信息,如com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused。需要注意的是,這個是預期的情況,因爲apollo-configservice需要向Meta Server(它自己)註冊服務,但是因爲在啓動過程中,自己還沒起來,所以會報這個錯。後面會進行重試的動作,所以等自己服務起來後就會註冊正常了。

4、啓動apollo-portal

application啓動配置如下:
在這裏插入圖片描述

VM options:

-Denv=dev
-Ddev_meta=http://localhost:8080
-Dspring.profiles.active=github,auth
-Deureka.client.enabled=false
-Dhibernate.query.plan_cache_max_size=192
-Dserver.port=8170
-Dspring.datasource.url=jdbc:mysql://localhost:3309/ApolloPortalDB?characterEncoding=utf8
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/logsh/apollo-portal.log

Program arguments:

–portal

這些配置也同樣參考官方的項目apollo-build-scripts項目的deom.sh腳本如下:不需要完全看懂所有意思,只需看重點如下:

 #portal service所需的VM options配置參數
PORTAL_JAVA_OPTS="$BASE_JAVA_OPTS -Ddev_meta=$config_server_url -Dspring.profiles.active=github,auth -Deureka.client.enabled=false -Dhibernate.query.plan_cache_max_size=192"

echo "==== starting portal ===="
echo "Portal logging file is $PORTAL_LOG"
# 導入VM options配置參數
export JAVA_OPTS="$PORTAL_JAVA_OPTS -Dlogging.file=./apollo-portal.log -Dserver.port=8070 -Dspring.datasource.url=$apollo_portal_db_url -Dspring.datasource.username=$apollo_portal_db_username -Dspring.datasource.password=$apollo_portal_db_password"
 # 啓動portal service 的參數
$PORTAL_JAR start --portal

一、apollo整體架構

在這裏插入圖片描述
apollo服務端大概有3個服務:

  • Config Service提供配置的讀取、推送等功能,服務對象是Apollo客戶端
  • Admin Service提供配置的修改、發佈等功能,服務對象是Apollo Portal(管理界面)
  • Config Service和Admin Service都是多實例、無狀態部署,所以需要將自己註冊到Eureka中並保持心跳
  • 在Eureka之上我們架了一層Meta Server用於封裝Eureka的服務發現接口
  • Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Client側會做load balance、錯誤重試
  • Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Portal側會做load balance、錯誤重試
  • 爲了簡化部署,我們實際上會把Config Service、Eureka和Meta Server三個邏輯角色部署在同一個JVM進程中

二、portal service發佈配置後,如何通知client

1、配置發佈後的實時推送過程

步驟一、用戶在Portal操作配置發佈

步驟二、Portal調用Admin Service的接口操作發佈

步驟三、Admin Service發佈配置後,發送ReleaseMessage給各個Config Service

Admin Service發送ReleaseMessage方式:
1、 Admin Service在配置發佈後會往ReleaseMessage表插入一條消息記錄,消息內容就是配置發佈的AppId+Cluster+Namespace,參見DatabaseMessageSender
2、Config Service有一個線程會每秒掃描一次ReleaseMessage表,看看是否有新的消息記錄,參見ReleaseMessageScanner
3、Config Service如果發現有新的消息記錄,那麼就會通知到所有的消息監聽器(ReleaseMessageListener),如NotificationControllerV2,消息監聽器的註冊過程參見ConfigServiceAutoConfiguration
4、NotificationControllerV2得到配置發佈的AppId+Cluster+Namespace後,會通知對應的客戶端

步驟四、Config Service收到ReleaseMessage後,通知對應的客戶端

Config Service通知客戶端的實現方式:

1、客戶端會發起一個Http請求到Config Service的notifications/v2接口,也就是NotificationControllerV2,參見RemoteConfigLongPollService

2、NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起

3、如果在60秒內沒有該客戶端關心的配置發佈,那麼會返回Http狀態碼304給客戶端

4、如果有該客戶端關心的配置發佈,NotificationControllerV2會調用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回。客戶端從返回的結果中獲取到配置變化的namespace後,會立即請求Config Service獲取該namespace的最新配置。

三、客戶端監聽配置原理

1、客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現)

2、客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。

  • 這是一個fallback機制,爲了防止推送機制失效導致配置不更新
  • 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
  • 定時頻率默認爲每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位爲分鐘。

3、客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中

4、客戶端會把從服務端獲取到的配置在本地文件系統緩存一份

  • 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置

5、應用程序可以從Apollo客戶端獲取最新的配置、訂閱配置更新通知

四、整個流程

在這裏插入圖片描述

1、通知客戶端配置有更新:是通過 deferredResults 這個對象實現的。

deferredResults 對象被定成NotificationControllerV2類的成員變量。
客戶端會發起一個長連接到config service服務,url對應了NotificationControllerV2.#pollNotification 方法,並返回DeferredResult對象, 返回這個對象 ,那麼請求並不會立即返回,直到DeferredResult.setResult 被設置值 ,請求才會返回。
那麼這個對象DeferredResult.setResult 何時調用?

  • 當配置發佈—》
  • 插入消息表----》
  • config service 定時描消息表-----》
  • 有新的發佈消息-----》
  • 調用監聽方法NotificationControllerV2.#handleMessage-----》
  • 設置DeferredResult.setResult ------->
  • 客戶端調用的NotificationControllerV2.#pollNotification方法------》
  • 感受到DeferredResult.setResult 設置,立即返回,否則60s沒有配置更新 ,那麼超時返回304

2、核心方法如下:

@RestController
@RequestMapping("/notifications/v2")
public class NotificationControllerV2 implements ReleaseMessageListener {
    //定義deferredResults 成員變量,設置成同步集合,防止併發
    //DeferredResultWrapper類型是DeferredResult的包裝類,裏面持有DeferredResult類型的變量
    private final Multimap<String, DeferredResultWrapper> deferredResults =
      Multimaps.synchronizedSetMultimap(TreeMultimap.create(String.CASE_INSENSITIVE_ORDER, Ordering.natural()));
}
//客戶端定時獲取 最新配置url方法
@GetMapping
  public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> pollNotification(
      @RequestParam(value = "appId") String appId,
      @RequestParam(value = "cluster") String cluster,
      @RequestParam(value = "notifications") String notificationsAsString,
      @RequestParam(value = "dataCenter", required = false) String dataCenter,
      @RequestParam(value = "ip", required = false) String clientIp) {
    // 解析 notificationsAsString 參數,創建 ApolloConfigNotification 數組。
    List<ApolloConfigNotification> notifications = null;
    //創建deferredResultWrapper 對象
    DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper(bizConfig.longPollingTimeoutInMilli());  
    deferredResultWrapper.onCompletion(() -> {
      // 當這個對象resultWrapper完成setReuslt設置, 註銷已經返回的監聽
      for (String key : watchedKeys) {
        deferredResults.remove(key, deferredResultWrapper);
      }
    });
    //註冊DeferredResultWrapper 到 `deferredResults` 中,等待配置發生變化後通知。詳見 `#handleMessage(...)` 方法。
    for (String key : watchedKeys) {
      this.deferredResults.put(key, deferredResultWrapper);
    }
    //返回DeferredResult對象,直到#handleMessage監聽方法中設置了值返回
    //或等待超時返回304
    return deferredResultWrapper.getResult();
  }
//配置發佈後的通知方法
@Override
  public void handleMessage(ReleaseMessage message, String channel) {
    logger.info("message received - channel: {}, message: {}", channel, message);
    List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content));
    if (results.size() > bizConfig.releaseMessageNotificationBatch()) {
      largeNotificationBatchExecutorService.submit(() -> {
        logger.debug("Async notify {} clients for key {} with batch {}", results.size(), content,
            bizConfig.releaseMessageNotificationBatch());
        for (int i = 0; i < results.size(); i++) {
           //如果有配置更新,那麼調用了DeferredResultWrapper類對象的setResult方法,
           //這個方法就是獲取內部的成量DeferredResult 並設置值。
          results.get(i).setResult(configNotification);
        }
      });
      return;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章