Apollo 9 — adminService 主/灰度版本發佈

目錄

  1. Controller 層
  2. Service 層 publish 方法
  3. 發送 ReleaseMessage 消息
  4. 總結

1. Controller 層

主版本發佈即點擊主版本發佈按鈕:java

具體接口位置:com.ctrip.framework.apollo.adminservice.controller 包下 ReleaseController#publish
實際上灰度版本發佈也是調用這個接口的。
代碼:mysql

/**
   * 主版本發佈
   */
  @Transactional
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
  public ReleaseDTO publish(@PathVariable("appId") String appId,
                            @PathVariable("clusterName") String clusterName,
                            @PathVariable("namespaceName") String namespaceName,
                            @RequestParam("name") String releaseName,
                            @RequestParam(name = "comment", required = false) String releaseComment,
                            @RequestParam("operator") String operator,
                            @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
    // 校驗存在與否
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
    if (namespace == null) {
      throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
                                                clusterName, namespaceName));
    }
    // 發佈
    Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);

    //send release message 發送消息到 ReleaseMessage
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
    String messageCluster;
    if (parentNamespace != null) {
      messageCluster = parentNamespace.getClusterName();
    } else {
      messageCluster = clusterName;
    }
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
                              Topics.APOLLO_RELEASE_TOPIC);
    return BeanUtils.transfrom(ReleaseDTO.class, release);
  }

該層主要作了 2 件事情,1是調用 Service 層的 public 方法作真正的發佈操做,2是發送「發佈消息」到數據庫——等待 ConfigService 消費。git

因此,咱們主要關注 Service 層的 publish 方法。github

2. Service 層 publish 方法

該方法有些繁瑣,主要流程圖以下:sql

publish 流程圖

能夠經過比對流程圖和代碼來看。數據庫

代碼以下:併發

@Transactional
  public Release publish(Namespace namespace, String releaseName, String releaseComment,
                         String operator, boolean isEmergencyPublish) {
    // 檢查鎖
    checkLock(namespace, isEmergencyPublish, operator);
    // 獲取 item
    Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);

    // 根據當前 namespace 找到父 namespace, 也就是灰度的主版本.
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);

    //branch release // 父 namespace 不是 null, 說明當前就是灰度版本.
    if (parentNamespace != null) {
      // 發佈灰度版本.
      return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems,
                                    releaseName, releaseComment, operator, isEmergencyPublish);
    }

    // 非灰度版本, 找到子版本
    Namespace childNamespace = namespaceService.findChildNamespace(namespace);

    Release previousRelease = null;
    if (childNamespace != null) {
      // 找到上一個版本
      previousRelease = findLatestActiveRelease(namespace);
    }

    //master release
    Map<String, Object> operationContext = Maps.newHashMap();
    // 記錄是否緊急發佈
    operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
    // 主版本發佈
    Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
                                    operator, ReleaseOperation.NORMAL_RELEASE, operationContext);


    //merge to branch and auto release
    // 將主版本合併到灰度版本. 並自動發佈
    if (childNamespace != null) {
      mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems,
                                      releaseName, releaseComment, operator, previousRelease,
                                      release, isEmergencyPublish);
    }
    return release;
  }
  1. 檢查鎖:若是不是緊急發佈,就須要檢查鎖,若是這個 namespace 的最後修改者就是當前用戶,那麼就拋出異常。禁止其修改。app

  2. 根據 namespace 獲取全部的 item,也就是配置。ui

  3. 判斷當前的 namespace 是否有父 namespace,若是有,說明當前 namespace 是灰度 namespace,則進行灰度發佈(主版本發佈和灰度發佈邏輯不一樣)。spa

這裏說下父子 namespace 在 apollo 的設計:

主體E-R Diagram 圖片來自 apollo wiki

從圖中能夠看出,namespace 和 cluster 是多對一的關係,而 cluster 有個字段:ParentClusterId,也就是說,cluster 是有層級的。每當建立一個灰度配置,實際上,就是建立了一個新的 cluster,這個新的 cluster 的名字就是 時間戳-字符串,大概是這樣的:20180705150428-1dc5208dc9e8146b. 而後再在這個新 cluster 下面建立新的 namespace,那麼,namespace 無形中也有了層級(父子)關係。

  1. 若是沒有父 namespace,說明是主版本發佈,那麼就須要處理他的子 (灰度)版本,同時,爲了後面比對灰度版本和上一個版本的區別(若是灰度修改了上一個版本的數據,就須要記錄,不然,灰度數據和主版本將沒法對應),還要記錄上一個版本的 release 信息。

  2. 發佈主版本。並保存發佈歷史。

  3. 若是存在灰度版本,就更新灰度版本的配置,併發布灰度版本。

關於灰度版本,這裏多提一句,每次發佈都是一個 release,release 對象有個 configuration,包含了這次發佈的全量配置,所以,灰度發佈的 configuration 中,包含了每次對應的主版本的配置,若是主版本發生了變化,那麼灰度版本確定也是要變動的。因此須要從新發布灰度版本。

其中關鍵的方法就是 mergeConfiguration,該方法代表了灰度發佈的主要邏輯:

private Map<String, String> mergeConfiguration(Map<String, String> baseConfigurations,
                                                 Map<String, String> coverConfigurations) {
    Map<String, String> result = new HashMap<>();
    //copy base configuration
    for (Map.Entry<String, String> entry : baseConfigurations.entrySet()) {
      result.put(entry.getKey(), entry.getValue());
    }

    //update and publish
    for (Map.Entry<String, String> entry : coverConfigurations.entrySet()) {
      result.put(entry.getKey(), entry.getValue());
    }

    return result;
  }

方法很簡單:兩個參數,主版本配置,灰度版本配置。首先將主版本配置保存到 Map 中,而後將灰度版本配置也 put 到 Map 中,利用 Map 惟一 Key 的特性,保證灰度版本覆蓋主版本。

因此這個方法的 put 順序決定了灰度版本覆蓋主版本。

publish 方法更多的細節再也不贅述,有疑惑的地方能夠交流。

3. 發送 ReleaseMessage 消息

這個發送消息的操做原本應該是 MQ,apollo 爲了減小依賴,直接使用的 mysql,但已經留好了MQ 的設計。關於 ReleaseMessage 的設計,我這裏引用一下 apollo 的文檔:

Admin Service在配置發佈後,須要通知全部的Config Service有配置發佈,從而Config Service能夠通知對應的客戶端來拉取最新的配置。
從概念上來看,這是一個典型的消息使用場景,Admin Service做爲producer發出消息,各個Config Service做爲consumer消費消息。經過一個消息組件(Message Queue)就能很好的實現Admin Service和Config Service的解耦。
在實現上,考慮到Apollo的實際使用場景,以及爲了儘量減小外部依賴,咱們沒有采用外部的消息中間件,而是經過數據庫實現了一個簡單的消息隊列。
實現方式以下:

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

示意圖以下:

apollo 定義了 MessageSender 接口,定義了一個 sendMessage 方法,這個方法目前只有基於 Mysql 的實現,即 DatabaseMessageSender 實現類。

該類會將數據直接保存到數據庫。而後清理掉比剛剛存的消息舊的消息—— 防止消息表不斷增大。

4. 總結

發佈分爲主版本發佈,灰度版本發佈,全量發佈,此次說了前兩個,全量發佈下次再說。

而主/灰髮布的一個比較繁瑣的地方就是兩個版本的合併,灰度版本發佈要合併主版本。主版本發佈要更新灰度版本

同時,灰度的設計也有點繞,中間隔了一層 cluster。

在發佈成功以後,須要發送消息到數據庫,讓 ConfigService 可以感知到這次發佈,並通知客戶端。關於如何通知客戶端,下次再說。

相關文章
相關標籤/搜索