Apollo 源碼解析 —— Portal 發佈配置

點擊上方“芋道源碼”,選擇“設爲星標

做積極的人,而不是積極廢人!

源碼精品專欄

 

摘要: 原創出處 http://www.iocoder.cn/Apollo/portal-publish/ 「芋道源碼」歡迎轉載,保留摘要,謝謝!

  • 1. 概述

  • 2. 實體

  • 3. Portal 側

  • 4. Admin Service 側

  • 666. 彩蛋


1. 概述

老艿艿:本系列假定胖友已經閱讀過 《Apollo 官方 wiki 文檔》  。

從本文開始,我們進入 Apollo 最最最核心的流程 配置發佈後的實時推送設計 。

在配置中心中,一個重要的功能就是配置發佈後實時推送到客戶端。下面我們簡要看一下這塊是怎麼設計實現的。

配置發佈

上圖簡要描述了配置發佈的大致過程:

  1. 用戶在 Portal 操作配置發佈

  2. Portal 調用 Admin Service 的接口操作發佈

  3. Admin Service 發佈配置後,發送 ReleaseMessage 給各個Config Service

  4. Config Service 收到 ReleaseMessage 後,通知對應的客戶端

本文分享 Portal 發佈配置,對應上述第一、二步,大體流程如下:

流程
  • ???? 這個流程過程中,我們先不考慮灰度發佈,會涉及配置合併的過程。

老艿艿:因爲 Portal 是管理後臺,所以從代碼實現上,和業務系統非常相像。也因此,本文會略顯囉嗦。

2. 實體

2.1 Release

com.ctrip.framework.apollo.biz.entity.Release ,繼承 BaseEntity 抽象類,Release 實體。代碼如下:

@Entity
@Table(name = "Release")
@SQLDelete(sql = "Update Release set isDeleted = 1 where id = ?") // 標記刪除
@Where(clause = "isDeleted = 0")
public class Release extends BaseEntity {

    /**
     * Release Key
     *
     * 【TODO 6006】用途?
     */
    @Column(name = "ReleaseKey", nullable = false)
    private String releaseKey;
    /**
     * 標題
     */
    @Column(name = "Name", nullable = false)
    private String name;
    /**
     * 備註
     */
    @Column(name = "Comment", nullable = false)
    private String comment;
    /**
     * App 編號
     */
    @Column(name = "AppId", nullable = false)
    private String appId;
    /**
     * Cluster 名字
     */
    @Column(name = "ClusterName", nullable = false)
    private String clusterName;
    /**
     * Namespace 名字
     */
    @Column(name = "NamespaceName", nullable = false)
    private String namespaceName;
    /**
     * 配置 Map 字符串,使用 JSON 格式化成字符串
     */
    @Column(name = "Configurations", nullable = false)
    @Lob
    private String configurations;
    /**
     * 是否被回滾(放棄)
     */
    @Column(name = "IsAbandoned", columnDefinition = "Bit default '0'")
    private boolean isAbandoned;
}
  • releaseKey 字段,【TODO 6006】用途?

  • name 字段,發佈標題。

  • comment 字段,發佈備註。

  • appId + clusterName + namespaceName 字段,指向對應的 Namespace 記錄。

  • configurations 字段,發佈時的完整配置 Map 字符串,使用 JSON 格式化成字符串。

    • x

    • Commit.changeSets 字段,格式一致,只是它是變化配置 Map 字符串

    • 例子如下:

      {"huidu01":"huidu01"}
      
  • isAbandoned 字段,是否被回滾(放棄)。

2.2 ReleaseHistory

com.ctrip.framework.apollo.biz.entity.ReleaseHistory ,繼承 BaseEntity 抽象類,ReleaseHistory 實體,記錄每次 Release 相關的操作日誌。代碼如下:

@Entity
@Table(name = "ReleaseHistory")
@SQLDelete(sql = "Update ReleaseHistory set isDeleted = 1 where id = ?") // 標記刪除
@Where(clause = "isDeleted = 0")
public class ReleaseHistory extends BaseEntity {

    /**
     * App 編號
     */
    @Column(name = "AppId", nullable = false)
    private String appId;
    /**
     * Cluster 名字
     */
    @Column(name = "ClusterName", nullable = false)
    private String clusterName;
    /**
     * Namespace 名字
     */
    @Column(name = "NamespaceName", nullable = false)
    private String namespaceName;
    /**
     * Branch 名
     *
     * 主幹,使用 Cluster 名字
     * 分支,使用子 Cluster 名字
     */
    @Column(name = "BranchName", nullable = false)
    private String branchName;
    /**
     * Release 編號
     */
    @Column(name = "ReleaseId")
    private long releaseId;
    /**
     * 上一次 Release 編號
     */
    @Column(name = "PreviousReleaseId")
    private long previousReleaseId;
    /**
     * 操作類型 {@link com.ctrip.framework.apollo.common.constants.ReleaseOperation}
     */
    @Column(name = "Operation")
    private int operation;
    /**
     * 操作 Context
     */
    @Column(name = "OperationContext", nullable = false)
    private String operationContext;
    
}
  • appId + clusterName + namespaceName 字段,指向對應的 Namespace 記錄。

  • branchName 字段,Branch 名字。

    • 主幹,使用 Cluster 名字。

    • 分支,使用 Cluster 名字。

  • releaseId 字段,Release 編號。

  • previousReleaseId 字段,上一次 Release 編號。

  • operation 類型,操作類型。在 com.ctrip.framework.apollo.common.constants.ReleaseOperation 類中,枚舉了所有發佈相關的操作類型。代碼如下:

    public interface ReleaseOperation {
    
        int NORMAL_RELEASE = 0; // 主幹發佈
        int ROLLBACK = 1; // 回滾
        int GRAY_RELEASE = 2; // 灰度發佈
        int APPLY_GRAY_RULES = 3; //
        int GRAY_RELEASE_MERGE_TO_MASTER = 4;
        int MASTER_NORMAL_RELEASE_MERGE_TO_GRAY = 5;
        int MATER_ROLLBACK_MERGE_TO_GRAY = 6;
        int ABANDON_GRAY_RELEASE = 7;
        int GRAY_RELEASE_DELETED_AFTER_MERGE = 8;
    
    }
    
  • operationContext 字段,操作 Context 。

2.3 ReleaseMessage

下一篇文章,詳細分享。

3. Portal 側

3.1 ReleaseController

apollo-portal 項目中,com.ctrip.framework.apollo.portal.controller.ReleaseController ,提供 Release 的 API

在【發佈】的界面中,點擊【 發佈 】按鈕,調用發佈配置的 API

發佈配置

#createRelease(appId, env, clusterName, namespaceName, NamespaceReleaseModel) 方法,發佈配置。代碼如下:

  1: @Autowired
  2: private ReleaseService releaseService;
  3: @Autowired
  4: private ApplicationEventPublisher publisher;
  5: @Autowired
  6: private PortalConfig portalConfig;
  7: 
  8: @PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)")
  9: @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
 10: public ReleaseDTO createRelease(@PathVariable String appId,
 11:                                 @PathVariable String env, @PathVariable String clusterName,
 12:                                 @PathVariable String namespaceName, @RequestBody NamespaceReleaseModel model) {
 13:     // 校驗 NamespaceReleaseModel 非空
 14:     checkModel(Objects.nonNull(model));
 15:     // 設置 PathVariable 變量到 NamespaceReleaseModel 中
 16:     model.setAppId(appId);
 17:     model.setEnv(env);
 18:     model.setClusterName(clusterName);
 19:     model.setNamespaceName(namespaceName);
 20:     // 若是緊急發佈,但是當前環境未允許該操作,拋出 BadRequestException 異常
 21:     if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.valueOf(env))) {
 22:         throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));
 23:     }
 24:     // 發佈配置
 25:     ReleaseDTO createdRelease = releaseService.publish(model);
 26: 
 27:     // 創建 ConfigPublishEvent 對象
 28:     ConfigPublishEvent event = ConfigPublishEvent.instance();
 29:     event.withAppId(appId)
 30:             .withCluster(clusterName)
 31:             .withNamespace(namespaceName)
 32:             .withReleaseId(createdRelease.getId())
 33:             .setNormalPublishEvent(true)
 34:             .setEnv(Env.valueOf(env));
 35:     // 發佈 ConfigPublishEvent 事件
 36:     publisher.publishEvent(event);
 37: 
 38:     return createdRelease;
 39: }
  • POST /apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases 接口,Request Body 傳遞 JSON 對象。

  • @PreAuthorize(...) 註解,調用 PermissionValidator#hasReleaseNamespacePermissio(appId, namespaceName) 方法,校驗是否有發佈配置的權限。後續文章,詳細分享。

  • com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel ,Namespace 配置發佈 Model 。代碼如下:

    public class NamespaceReleaseModel implements Verifiable {
    
        /**
         * App 編號
         */
        private String appId;
        /**
         * Env 名字
         */
        private String env;
        /**
         * Cluster 名字
         */
        private String clusterName;
        /**
         * Namespace 名字
         */
        private String namespaceName;
        /**
         * 發佈標題
         */
        private String releaseTitle;
        /**
         * 發佈描述
         */
        private String releaseComment;
        /**
         * 發佈人
         */
        private String releasedBy;
        /**
         * 是否緊急發佈
         */
        private boolean isEmergencyPublish;
    
        @Override
        public boolean isInvalid() {
            return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName, releaseTitle); // 校驗非空
        }
        
    }
    
  • 第 14 行:校驗 NamespaceReleaseModel 非空。

  • 第 15 至 19 行:設置 PathVariable 變量到 NamespaceReleaseModel 中。

  • 第 20 至 23 行:校驗若是緊急發佈,但是當前環境未允許該操作,拋出 BadRequestException 異常。

    • 緊急發佈功能,可通過設置 PortalDB 的 ServerConfig 的"emergencyPublish.supported.envs" 配置開啓對應的 Env 們。例如,emergencyPublish.supported.envs = dev

  • 第 25 行:調用 ReleaseService#publish(NamespaceReleaseModel) 方法,調用 Admin Service API ,發佈配置。

  • 第 27 至 36 行:創建 ConfigPublishEvent 對象,並調用 ApplicationEventPublisher#publishEvent(event) 方法,發佈 ConfigPublishEvent 事件。這部分,我們在後續文章分享。

  • 第 38 行:返回 ReleaseDTO 對象。

3.2 ReleaseService

apollo-portal 項目中,com.ctrip.framework.apollo.portal.service.ReleaseService ,提供 Release 的 Service 邏輯。

#publish(NamespaceReleaseModel) 方法,調用 Admin Service API ,發佈配置。代碼如下:

  1: @Autowired
  2: private UserInfoHolder userInfoHolder;
  3: @Autowired
  4: private AdminServiceAPI.ReleaseAPI releaseAPI;
  5: 
  6: public ReleaseDTO publish(NamespaceReleaseModel model) {
  7:     Env env = model.getEnv();
  8:     boolean isEmergencyPublish = model.isEmergencyPublish();
  9:     String appId = model.getAppId();
 10:     String clusterName = model.getClusterName();
 11:     String namespaceName = model.getNamespaceName();
 12:     String releaseBy = StringUtils.isEmpty(model.getReleasedBy()) ? userInfoHolder.getUser().getUserId() : model.getReleasedBy();
 13: 
 14:     // 調用 Admin Service API ,發佈 Namespace 的配置。
 15:     ReleaseDTO releaseDTO = releaseAPI.createRelease(appId, env, clusterName, namespaceName,
 16:             model.getReleaseTitle(), model.getReleaseComment(),
 17:             releaseBy, isEmergencyPublish);
 18:     // 【TODO 6001】Tracer 日誌
 19:     Tracer.logEvent(TracerEventType.RELEASE_NAMESPACE, String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
 20:     return releaseDTO;
 21: }
  • 第14 至 17 行:調用 ReleaseAPI#createRelease(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, releaseBy, isEmergencyPublish) 方法,調用 Admin Service API ,發佈配置。

  • 第 19 行:【TODO 6001】Tracer 日誌

3.3 ReleaseAPI

com.ctrip.framework.apollo.portal.api.ReleaseAPI ,實現 API 抽象類,封裝對 Admin Service 的 Release 模塊的 API 調用。代碼如下:

ReleaseAPI

4. Admin Service 側

4.1 ReleaseController

apollo-adminservice 項目中, com.ctrip.framework.apollo.adminservice.controller.ReleaseController ,提供 Release 的 API

#publish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, releaseBy, isEmergencyPublish) 方法,發佈 Namespace 的配置。代碼如下:

  1: @Autowired
  2: private ReleaseService releaseService;
  3: @Autowired
  4: private NamespaceService namespaceService;
  5: @Autowired
  6: private MessageSender messageSender;
  7: 
  8: @Transactional
  9: @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
 10: public ReleaseDTO publish(@PathVariable("appId") String appId,
 11:                           @PathVariable("clusterName") String clusterName,
 12:                           @PathVariable("namespaceName") String namespaceName,
 13:                           @RequestParam("name") String releaseName,
 14:                           @RequestParam(name = "comment", required = false) String releaseComment,
 15:                           @RequestParam("operator") String operator,
 16:                           @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
 17:     // 校驗對應的 Namespace 對象是否存在。若不存在,拋出 NotFoundException 異常
 18:     Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
 19:     if (namespace == null) {
 20:         throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId, clusterName, namespaceName));
 21:     }
 22:     // 發佈 Namespace 的配置
 23:     Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);
 24: 
 25:     // send release message
 26:     // 獲得 Cluster 名
 27:     Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
 28:     String messageCluster;
 29:     if (parentNamespace != null) { // 灰度發佈
 30:         messageCluster = parentNamespace.getClusterName();
 31:     } else {
 32:         messageCluster = clusterName; // 使用請求的 ClusterName
 33:     }
 34:     // 發送 Release 消息
 35:     messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName), Topics.APOLLO_RELEASE_TOPIC);
 36: 
 37:     // 將 Release 轉換成 ReleaseDTO 對象
 38:     return BeanUtils.transfrom(ReleaseDTO.class, release);
 39: }
  • POST /apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases 接口,Request Body 傳遞 JSON 對象。

  • 第 17 至 21 行:校驗對應的 Namespace 對象是否存在。若不存在,拋出 NotFoundException 異常。

  • 第 23 行:調用 ReleaseService#publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish) 方法,發佈 Namespace 的配置,返回  Release 對象。

  • 第 26 至 33 行:獲得發佈消息的 Cluster 名字。

    • 這塊胖友可以先跳過,等看完後面灰度發佈相關的內容,在回過頭理解。

    • 第 27 行:調用 NamespaceService#findParentNamespace(namespace) 方法,獲得 Namespace 對象。代碼如下:

      @Autowired
      private ClusterService clusterService;
      @Autowired
      private ClusterService clusterService;
          
      public Namespace findParentNamespace(Namespace namespace) {
          String appId = namespace.getAppId();
          String namespaceName = namespace.getNamespaceName();
          // 獲得 Cluster
          Cluster cluster = clusterService.findOne(appId, namespace.getClusterName());
          // 若爲子 Cluster
          if (cluster != null && cluster.getParentClusterId() > 0) {
              // 獲得父 Cluster
              Cluster parentCluster = clusterService.findOne(cluster.getParentClusterId());
              // 獲得父 Namespace
              return findOne(appId, parentCluster.getName(), namespaceName);
          }
          return null;
      }
      
      public Namespace findOne(String appId, String clusterName, String namespaceName) {
          return namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName, namespaceName);
      }
      
    • 第 29 至 30 行:若有 Namespace 對象,說明是 Namespace ( 灰度發佈 ),則使用 Namespace 的 Cluster 名字。因爲,客戶端即使在灰度發佈的情況下,也是使用 Namespace 的 Cluster 名字。也就說,灰度發佈,對客戶端是透明無感知的。

    • 第 32 行:使用請求的 Cluster 名字。

  • 第 35 行:調用 MessageSender#sendMessage(String message, String channel) 方法,發送發佈消息。詳細實現,下一篇文章詳細解析。

  • 第 38 行:調用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,將 Release 轉換成 ReleaseDTO 對象。

4.2 ReleaseService

apollo-biz 項目中,com.ctrip.framework.apollo.biz.service.ReleaseService ,提供 Release  的 Service 邏輯給 Admin Service 和 Config Service 。

4.2.1 publish

#publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish) 方法,發佈 Namespace 的配置。代碼如下:

  1: private Gson gson = new Gson();
  2: 
  3: @Autowired
  4: private ReleaseRepository releaseRepository;
  5: @Autowired
  6: private ItemService itemService;
  7: @Autowired
  8: private AuditService auditService;
  9: @Autowired
 10: private NamespaceLockService namespaceLockService;
 11: @Autowired
 12: private NamespaceService namespaceService;
 13: @Autowired
 14: private ReleaseHistoryService releaseHistoryService;
 15: 
 16: @Transactional
 17: public Release publish(Namespace namespace, String releaseName, String releaseComment, String operator, boolean isEmergencyPublish) {
 18:     // 校驗鎖定
 19:     checkLock(namespace, isEmergencyPublish, operator);
 20:     // 獲得 Namespace 的普通配置 Map
 21:     Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);
 22:     // 獲得父 Namespace
 23:     Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
 24:     // 若有父 Namespace ,則是子 Namespace ,進行灰度發佈
 25:     // branch release
 26:     if (parentNamespace != null) {
 27:         return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems, releaseName, releaseComment, operator, isEmergencyPublish);
 28:     }
 29:     // 獲得子 Namespace 對象
 30:     Namespace childNamespace = namespaceService.findChildNamespace(namespace);
 31:     // 獲得上一次,並且有效的 Release 對象
 32:     Release previousRelease = null;
 33:     if (childNamespace != null) {
 34:         previousRelease = findLatestActiveRelease(namespace);
 35:     }
 36:     // 創建操作 Context
 37:     // master release
 38:     Map<String, Object> operationContext = Maps.newHashMap();
 39:     operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
 40:     // 主幹發佈
 41:     Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, ReleaseOperation.NORMAL_RELEASE, operationContext); // 是否緊急發佈。
 42:     // 若有子 Namespace 時,自動將主幹合併到子 Namespace ,並進行一次子 Namespace 的發佈
 43:     // merge to branch and auto release
 44:     if (childNamespace != null) {
 45:         mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems,
 46:                 releaseName, releaseComment, operator, previousRelease,
 47:                 release, isEmergencyPublish);
 48:     }
 49:     return release;
 50: }
  • 第 19 行:調用 #checkLock(namespace, isEmergencyPublish, operator) 方法,校驗 NamespaceLock 鎖定。代碼如下:

    private void checkLock(Namespace namespace, boolean isEmergencyPublish, String operator) {
        if (!isEmergencyPublish) { // 非緊急發佈
            // 獲得 NamespaceLock 對象
            NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
            // 校驗鎖定人是否是當前管理員。若是,拋出 BadRequestException 異常
            if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) {
                throw new BadRequestException("Config can not be published by yourself.");
            }
        }
    }
    
  • 第 21 行:調用 #getNamespaceItems(namespace) 方法,獲得 Namespace 的普通配置 Map 。代碼如下:

    private Map<String, String> getNamespaceItems(Namespace namespace) {
        // 讀取 Namespace 的 Item 集合
        List<Item> items = itemService.findItemsWithoutOrdered(namespace.getId());
        // 生成普通配置 Map 。過濾掉註釋和空行的配置項
        Map<String, String> configurations = new HashMap<String, String>();
        for (Item item : items) {
            if (StringUtils.isEmpty(item.getKey())) {
                continue;
            }
            configurations.put(item.getKey(), item.getValue());
        }
        return configurations;
    }
    
  • 第 23 行:調用 #findParentNamespace(namespace) 方法,獲得 Namespace 對象。

  • 第 26 至 28 行:若有 Namespace 對象,灰度發佈。詳細解析,見 《Apollo 源碼解析 —— Portal 灰度發佈》 。

  • 第 30 行:調用 NamespaceService#findChildNamespace(namespace) 方法,獲得子 Namespace 對象。詳細解析,見 《Apollo 源碼解析 —— Portal 創建灰度》 。

  • 第 31 至 35 行:調用 #findLatestActiveRelease(Namespace) 方法,獲得上一次,並且有效的 Release 對象。代碼如下:

    public Release findLatestActiveRelease(Namespace namespace) {
        return findLatestActiveRelease(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName());
    }
    
    public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) {
        return releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId,
                clusterName, namespaceName); // IsAbandoned = False && Id DESC
    }
    
  • 第 36 至 39 行:創建操作 Context

  • 第 41 行:調用 #masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, releaseOperation, operationContext) 方法,主幹發佈配置。???? 創建的 Namespace ,默認就是主幹,而灰度發佈使用的是分支

  • 第 42 至 48 行:調用 #mergeFromMasterAndPublishBranch(...) 方法,若有子 Namespace 時,自動將主幹合併到子 Namespace ,並進行一次子 Namespace 的發佈。

  • 第 49 行:返回 Release 對象。詳細解析,見 《Apollo 源碼解析 —— Portal 灰度發佈》 。

4.2.2 masterRelease

#masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, releaseOperation, operationContext) 方法,主幹發佈配置。代碼如下:

  1: private Release masterRelease(Namespace namespace, String releaseName, String releaseComment,
  2:                               Map<String, String> configurations, String operator,
  3:                               int releaseOperation, Map<String, Object> operationContext) {
  4:     // 獲得最後有效的 Release 對象
  5:     Release lastActiveRelease = findLatestActiveRelease(namespace);
  6:     long previousReleaseId = lastActiveRelease == null ? 0 : lastActiveRelease.getId();
  7:     // 創建 Release 對象,並保存
  8:     Release release = createRelease(namespace, releaseName, releaseComment, configurations, operator);
  9: 
 10:     // 創建 ReleaseHistory 對象,並保存
 11:     releaseHistoryService.createReleaseHistory(namespace.getAppId(), namespace.getClusterName(),
 12:             namespace.getNamespaceName(), namespace.getClusterName(),
 13:             release.getId(), previousReleaseId, releaseOperation,
 14:             operationContext, operator);
 15:     return release;
 16: }
  • 第 5 行:調用 #findLatestActiveRelease(namespace) 方法,獲得最後有效的 Release 對象。代碼如下:

    public Release findLatestActiveRelease(Namespace namespace) {
        return findLatestActiveRelease(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName());
    }
    
    public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) {
        return releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId,
                clusterName, namespaceName); // IsAbandoned = False && Id DESC
    }
    
  • 第 8 行:調用 #createRelease(namespace, releaseName, releaseComment, configurations, operator) 方法,創建 Release 對象,並保存。

  • 第10 至 14 行:調用 ReleaseHistoryService#createReleaseHistory(appId, clusterName, namespaceName, branchName, releaseId, previousReleaseId, operation, operationContext, operator) 方法,創建 ReleaseHistory 對象,並保存。

4.2.3 createRelease

#createRelease(namespace, releaseName, releaseComment, configurations, operator) 方法,創建 Release 對象,並保存。代碼如下:

  1: private Release createRelease(Namespace namespace, String name, String comment,
  2:                               Map<String, String> configurations, String operator) {
  3:     // 創建 Release 對象
  4:     Release release = new Release();
  5:     release.setReleaseKey(ReleaseKeyGenerator.generateReleaseKey(namespace)); //【TODO 6006】Release Key 用途?
  6:     release.setDataChangeCreatedTime(new Date());
  7:     release.setDataChangeCreatedBy(operator);
  8:     release.setDataChangeLastModifiedBy(operator);
  9:     release.setName(name);
 10:     release.setComment(comment);
 11:     release.setAppId(namespace.getAppId());
 12:     release.setClusterName(namespace.getClusterName());
 13:     release.setNamespaceName(namespace.getNamespaceName());
 14:     release.setConfigurations(gson.toJson(configurations)); // 使用 Gson ,將配置 Map 格式化成字符串。
 15:     // 保存 Release 對象
 16:     release = releaseRepository.save(release);
 17:     // 釋放 NamespaceLock
 18:     namespaceLockService.unlock(namespace.getId());
 19:     // 記錄 Audit 到數據庫中
 20:     auditService.audit(Release.class.getSimpleName(), release.getId(), Audit.OP.INSERT, release.getDataChangeCreatedBy());
 21:     return release;
 22: }
  • 第 4 至 14 行:創建 Release 對象,並設置對應的屬性。

    • 第 5 行:【TODO 6006】Release Key 用途?

    • 第 14 行:調用 Gson#toJson(src) 方法,將配置 Map 格式化成字符串。

  • 第 16 行:調用 ReleaseRepository#save(Release) 方法,保存 Release 對象。

  • 第 18 行:調用 NamespaceLockService#unlock(namespaceId) 方法,釋放 NamespaceLock 。

  • 第 20 行:記錄 Audit 到數據庫中。

4.3 ReleaseRepository

com.ctrip.framework.apollo.biz.repository.ReleaseRepository ,繼承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 Release 的數據訪問 給 Admin Service 和 Config Service 。代碼如下:

public interface ReleaseRepository extends PagingAndSortingRepository<Release, Long> {

  Release findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName, @Param("namespaceName") String namespaceName);

  Release findByIdAndIsAbandonedFalse(long id);

  List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);

  List<Release> findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);

  List<Release> findByReleaseKeyIn(Set<String> releaseKey);

  List<Release> findByIdIn(Set<Long> releaseIds);

  @Modifying
  @Query("update Release set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
  int batchDelete(String appId, String clusterName, String namespaceName, String operator);

  // For release history conversion program, need to delete after conversion it done
  List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdAsc(String appId, String clusterName, String namespaceName);

}

4.4 ReleaseHistoryService

apollo-biz 項目中,com.ctrip.framework.apollo.biz.service.ReleaseHistoryService ,提供 ReleaseHistory  的 Service 邏輯給 Admin Service 和 Config Service 。

#createReleaseHistory(appId, clusterName, namespaceName, branchName, releaseId, previousReleaseId, operation, operationContext, operator) 方法,創建 ReleaseHistory 對象,並保存。代碼如下:

  1: private Gson gson = new Gson();
  2: 
  3: @Autowired
  4: private ReleaseHistoryRepository releaseHistoryRepository;
  5: @Autowired
  6: private AuditService auditService;
  7: 
  8: @Transactional
  9: public ReleaseHistory createReleaseHistory(String appId, String clusterName, String namespaceName, String branchName,
 10:                                            long releaseId, long previousReleaseId, int operation,
 11:                                            Map<String, Object> operationContext, String operator) {
 12:     // 創建 ReleaseHistory 對象
 13:     ReleaseHistory releaseHistory = new ReleaseHistory();
 14:     releaseHistory.setAppId(appId);
 15:     releaseHistory.setClusterName(clusterName);
 16:     releaseHistory.setNamespaceName(namespaceName);
 17:     releaseHistory.setBranchName(branchName);
 18:     releaseHistory.setReleaseId(releaseId); // Release 編號
 19:     releaseHistory.setPreviousReleaseId(previousReleaseId); // 上一個 Release 編號
 20:     releaseHistory.setOperation(operation);
 21:     if (operationContext == null) {
 22:         releaseHistory.setOperationContext("{}"); //default empty object
 23:     } else {
 24:         releaseHistory.setOperationContext(gson.toJson(operationContext));
 25:     }
 26:     releaseHistory.setDataChangeCreatedTime(new Date());
 27:     releaseHistory.setDataChangeCreatedBy(operator);
 28:     releaseHistory.setDataChangeLastModifiedBy(operator);
 29:     // 保存 ReleaseHistory 對象
 30:     releaseHistoryRepository.save(releaseHistory);
 31:     // 記錄 Audit 到數據庫中
 32:     auditService.audit(ReleaseHistory.class.getSimpleName(), releaseHistory.getId(), Audit.OP.INSERT, releaseHistory.getDataChangeCreatedBy());
 33:     return releaseHistory;
 34: }
  • 第 12 至 28 行:創建 ReleaseHistory 對象,並設置對應的屬性。

  • 第 30 行:調用 ReleaseHistoryRepository#save(ReleaseHistory) 方法,保存 ReleaseHistory 對象。

  • 第 32 行:記錄 Audit 到數據庫中。

4.5 ReleaseHistoryRepository

com.ctrip.framework.apollo.biz.repository.ReleaseHistoryRepository ,繼承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 ReleaseHistory 的數據訪問 給 Admin Service 和 Config Service 。代碼如下:

public interface ReleaseHistoryRepository extends PagingAndSortingRepository<ReleaseHistory, Long> {

    Page<ReleaseHistory> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String
            clusterName, String namespaceName, Pageable pageable);

    Page<ReleaseHistory> findByReleaseIdAndOperationOrderByIdDesc(long releaseId, int operation, Pageable pageable);

    Page<ReleaseHistory> findByPreviousReleaseIdAndOperationOrderByIdDesc(long previousReleaseId, int operation, Pageable pageable);

    @Modifying
    @Query("update ReleaseHistory set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
    int batchDelete(String appId, String clusterName, String namespaceName, String operator);

}

666. 彩蛋

T T 終於要到比較乾的地方啦。



歡迎加入我的知識星球,一起探討架構,交流源碼。加入方式,長按下方二維碼噢

已在知識星球更新源碼解析如下:

最近更新《芋道 SpringBoot 2.X 入門》系列,已經 20 餘篇,覆蓋了 MyBatis、Redis、MongoDB、ES、分庫分表、讀寫分離、SpringMVC、Webflux、權限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能測試等等內容。

提供近 3W 行代碼的 SpringBoot 示例,以及超 4W 行代碼的電商微服務項目。

獲取方式:點“在看”,關注公衆號並回復 666 領取,更多內容陸續奉上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章