配置中心 Apollo 源码解析 —— Portal 发布配置

2,092 阅读13分钟
自我表扬:《Dubbo 实现原理与源码解析 —— 精品合集》
表扬自己:《D数据库实体设计合集》

摘要: 原创出处 www.iocoder.cn/Apollo/port… 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

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 格式化成字符串。

    • Commit.changeSets 字段,格式一致,只是它是变化配置 Map 字符串
    • 例子如下:

      {"huidu01":"huidu01"}
      
      • x
  • 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 终于要到比较干的地方啦。

知识星球