1. 程式人生 > >Apollo 9 — adminService 主/灰度版本發布

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

bean 方式 framework eas com 沒有 context config eth

目錄

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

1. Controller 層

主版本發布即點擊主版本發布按鈕:

技術分享圖片

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

  /**
   * 主版本發布
   */
  @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 消費。

所以,我們主要關註 Service 層的 publish 方法。

2. Service 層 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 的最後修改者就是當前用戶,那麽就拋出異常。禁止其修改。

  2. 根據 namespace 獲取所有的 item,也就是配置。

  3. 判斷當前的 namespace 是否有父 namespace,如果有,說明當前 namespace 是灰度 namespace,則進行灰度發布(主版本發布和灰度發布邏輯不同)。

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

技術分享圖片

從圖中可以看出,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 能夠感知到此次發布,並通知客戶端。關於如何通知客戶端,下次再說。

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