配置中心 Apollo 源码解析 —— Portal 创建灰度

自我表扬:《Dubbo 实现原理与源码解析 —— 精品合集》

表扬自己:《D数据库实体设计合集》

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

《配置中心 Apollo 源码解析 —— Portal 创建灰度》

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

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

1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Apollo 官方 wiki 文档 —— 灰度发布使用指南》

本文分享 Portal 创建灰度 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:《配置中心 Apollo 源码解析 —— Portal 创建灰度》

创建灰度,调用的是创建 Namespace 分支 的 API 。通过创建的子 Namespace ,可以关联其自己定义的 Cluster、Item、Release 等等。关系如下所图所示:《配置中心 Apollo 源码解析 —— Portal 创建灰度》

  • 创建 Namespace 分支时:
    • 会创建 Cluster ,指向 Cluster 。
    • 会创建 Namespace ,关联 Namespace 。实际上, Namespace 和 Namespace 无任何数据字段上的关联。
  • Namespace 添加 Item 时,该 Item 指向 Namespace 。虽然,代码实现和 Namespace 是一模一样的。
  • Namespace 发布( 灰度发布 ) 和 Namespace 发布( 普通发布 ) 在代码实现,有一些差距,后续文章分享。

老艿艿:在目前 Apollo 的实现上,胖友可以把分支灰度等价。

  • 所以下文在用词时,选择使用分支
  • 所以下文在用词时,选择使用分支
  • 所以下文在用词时,选择使用分支

🙂 这样的设计,巧妙。

2. Portal 侧

2.1 NamespaceBranchController

apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.NamespaceBranchController ,提供 Namespace 分支API

首先点击 application namespace 右上角的【创建灰度】按钮。

《配置中心 Apollo 源码解析 —— Portal 创建灰度》

点击确定后,灰度版本就创建成功了,页面会自动切换到【灰度版本】 Tab 。

《配置中心 Apollo 源码解析 —— Portal 创建灰度》

#createBranch(...) 方法,创建 Namespace 分支。代码如下:

@RestController
public class NamespaceBranchController {

    @Autowired
    private NamespaceBranchService namespaceBranchService;
    
    @PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName)")
    @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches", method = RequestMethod.POST)
    public NamespaceDTO createBranch(@PathVariable String appId,
                                     @PathVariable String env,
                                     @PathVariable String clusterName,
                                     @PathVariable String namespaceName) {
        return namespaceBranchService.createBranch(appId, Env.valueOf(env), clusterName, namespaceName);
    }
    
    // ... 省略其他接口和属性
}
    • POST "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches 接口 。
  • @PreAuthorize(...) 注解,调用 PermissionValidator#hasModifyNamespacePermission(appId, namespaceName) 方法,校验是否有修改 Namespace 的权限。后续文章,详细分享。
  • 调用 NamespaceBranchService#createBranch(...) 方法,创建 Namespace 分支

2.2 NamespaceBranchService

apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.NamespaceBranchService ,提供 Namespace 分支Service 逻辑。

#createItem(appId, env, clusterName, namespaceName, ItemDTO) 方法,创建并保存 Item 到 Admin Service 。代码如下:

 1: @Autowired
 2: private UserInfoHolder userInfoHolder;
 3: @Autowired
 4: private AdminServiceAPI.NamespaceBranchAPI namespaceBranchAPI;
 5: 
 6: @Transactional
 7: public NamespaceDTO createBranch(String appId, Env env, String parentClusterName, String namespaceName) {
 8:     // 创建 Namespace 分支
 9:     NamespaceDTO createdBranch = namespaceBranchAPI.createBranch(appId, env, parentClusterName, namespaceName, userInfoHolder.getUser().getUserId());
10:     // 【TODO 6001】Tracer 日志
11:     Tracer.logEvent(TracerEventType.CREATE_GRAY_RELEASE, String.format("%s+%s+%s+%s", appId, env, parentClusterName, namespaceName));
12:     return createdBranch;
13: }
  • 第 9 行:调用 NamespaceBranchAPI#createBranch(...) 方法,创建 Namespace 分支
  • 第 11 行:【TODO 6001】Tracer 日志

2.3 NamespaceBranchAPI

com.ctrip.framework.apollo.portal.api.NamespaceBranchAPI ,实现 API 抽象类,封装对 Admin Service 的 Namespace 分支模块的 API 调用。代码如下:

《配置中心 Apollo 源码解析 —— Portal 创建灰度》

3. Admin Service 侧

3.1 NamespaceBranchController

apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.NamespaceBranchController ,提供 Namespace 分支API

#createBranch(...) 方法,创建 Namespace 分支。代码如下:

 1: @RestController
 2: public class NamespaceBranchController {
 3: 
 4:     @Autowired
 5:     private MessageSender messageSender;
 6:     @Autowired
 7:     private NamespaceBranchService namespaceBranchService;
 8:     @Autowired
 9:     private NamespaceService namespaceService;
10: 
11:     @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches", method = RequestMethod.POST)
12:     public NamespaceDTO createBranch(@PathVariable String appId,
13:                                      @PathVariable String clusterName,
14:                                      @PathVariable String namespaceName,
15:                                      @RequestParam("operator") String operator) {
16:         // 校验 Namespace 是否存在
17:         checkNamespace(appId, clusterName, namespaceName);
18:         // 创建子 Namespace
19:         Namespace createdBranch = namespaceBranchService.createBranch(appId, clusterName, namespaceName, operator);
20:         // 将 Namespace 转换成 NamespaceDTO 对象
21:         return BeanUtils.transfrom(NamespaceDTO.class, createdBranch);
22:     }
23:     
24:     // ... 省略其他接口和属性
25: }
  • 第 17 行:调用 #checkNamespace(appId, clusterName, namespaceName)校验父 Namespace 是否存在。代码如下:

    private void checkNamespace(String appId, String clusterName, String namespaceName) {
        // 查询父 Namespace 对象
        Namespace parentNamespace = namespaceService.findOne(appId, clusterName, namespaceName);
        // 若父 Namespace 不存在,抛出 BadRequestException 异常
        if (parentNamespace == null) {
            throw new BadRequestException(String.format("Namespace not exist. AppId = %s, ClusterName = %s, NamespaceName = %s",
                    appId, clusterName, namespaceName));
        }
    }
    
  • 第 19 行:调用 NamespaceBranchService#createBranch(appId, clusterName, namespaceName, operator) 方法,创建 Namespace 分支

  • 第 21 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 Namespace 转换成 NamespaceDTO 对象。

3.2 NamespaceBranchService

apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.NamespaceBranchService ,提供 Namespace 分支Service 逻辑给 Admin Service 和 Config Service 。

#createBranch(appId, clusterName, namespaceName, operator) 方法,创建 Namespace 分支。即,新增 Cluster 和 Namespace 。代码如下:

 1: @Autowired
 2: private ClusterService clusterService;
 3: @Autowired
 4: private NamespaceService namespaceService;
 5: 
 6: @Transactional
 7: public Namespace createBranch(String appId, String parentClusterName, String namespaceName, String operator) {
 8:     // 获得子 Namespace 对象
 9:     Namespace childNamespace = findBranch(appId, parentClusterName, namespaceName);
10:     // 若存在子 Namespace 对象,则抛出 BadRequestException 异常。一个 Namespace 有且仅允许有一个子 Namespace 。
11:     if (childNamespace != null) {
12:         throw new BadRequestException("namespace already has branch");
13:     }
14:     // 获得父 Cluster 对象
15:     Cluster parentCluster = clusterService.findOne(appId, parentClusterName);
16:     // 若父 Cluster 对象不存在,抛出 BadRequestException 异常
17:     if (parentCluster == null || parentCluster.getParentClusterId() != 0) {
18:         throw new BadRequestException("cluster not exist or illegal cluster");
19:     }
20: 
21:     // 创建子 Cluster 对象
22:     // create child cluster
23:     Cluster childCluster = createChildCluster(appId, parentCluster, namespaceName, operator);
24:     // 保存子 Cluster 对象
25:     Cluster createdChildCluster = clusterService.saveWithoutInstanceOfAppNamespaces(childCluster);
26: 
27:     // 创建子 Namespace 对象
28:     // create child namespace
29:     childNamespace = createNamespaceBranch(appId, createdChildCluster.getName(), namespaceName, operator);
30:     // 保存子 Namespace 对象
31:     return namespaceService.save(childNamespace);
32: }
  • 第 9 行:调用 #findBranch(appId, parentClusterName, namespaceName) 方法,获得 Namespace 对象。详细解析,见 「3.2.1 findBranch」
  • 第 10 至 13 行:校验若存在 Namespace 对象,则抛出 BadRequestException 异常。一个 Namespace 有且仅允许有一个子 Namespace
  • 第 15 行:调用 ClusterService#findOne(appId, parentClusterName) 方法,获得 Cluster 对象。
  • 第 16 至 19 行:校验若父 Cluster 对象不存在,则抛出 BadRequestException 异常。
  • ========== 子 Cluster ==========
  • 第 23 行:调用 #createChildCluster(appId, parentCluster, namespaceName, operator) 方法,创建 Cluster 对象。详细解析,见 「3.2.2 createChildCluster」
  • 第 25 行:调用 ClusterService#saveWithoutInstanceOfAppNamespaces(Cluster) 方法,保存 Cluster 对象。
  • ========== 子 Namespace ==========
  • 第 29 行:调用 #createNamespaceBranch(appId, createdChildClusterName, namespaceName, operator) 方法,创建 Namespace 对象。详细解析,见 「3.2.3 createNamespaceBranch」
  • 第 31 行:调用 NamespaceService#save(childNamespace) 方法,保存 Namespace 对象。

3.2.1 findBranch

#findBranch(appId, parentClusterName, namespaceName) 方法,获得 Namespace 对象。代码如下:

public Namespace findBranch(String appId, String parentClusterName, String namespaceName) {
    return namespaceService.findChildNamespace(appId, parentClusterName, namespaceName);
}

NamespaceService#findChildNamespace(appId, parentClusterName, namespaceName) 方法,获得 Namespace 对象。代码如下:

 1: /**
 2:  * 获得指定父 Namespace 的子 Namespace 对象
 3:  *
 4:  * @param appId App 编号
 5:  * @param parentClusterName 父 Cluster 的名字
 6:  * @param namespaceName 父 Namespace 的名字
 7:  * @return 子 Namespace 对象
 8:  */
 9: public Namespace findChildNamespace(String appId, String parentClusterName, String namespaceName) {
10:     // 获得 Namespace 数组
11:     List<Namespace> namespaces = findByAppIdAndNamespaceName(appId, namespaceName);
12:     // 若只有一个 Namespace ,说明没有子 Namespace
13:     if (CollectionUtils.isEmpty(namespaces) || namespaces.size() == 1) {
14:         return null;
15:     }
16:     // 获得 Cluster 数组
17:     List<Cluster> childClusters = clusterService.findChildClusters(appId, parentClusterName);
18:     // 若无子 Cluster ,说明没有子 Namespace
19:     if (CollectionUtils.isEmpty(childClusters)) {
20:         return null;
21:     }
22:     // 创建子 Cluster 的名字的集合
23:     Set<String> childClusterNames = childClusters.stream().map(Cluster::getName).collect(Collectors.toSet());
24:     // 遍历 Namespace 数组,比较 Cluster 的名字。若符合,则返回该子 Namespace 对象。
25:     // the child namespace is the intersection of the child clusters and child namespaces
26:     for (Namespace namespace : namespaces) {
27:         if (childClusterNames.contains(namespace.getClusterName())) {
28:             return namespace;
29:         }
30:     }
31:     // 无子 Namespace ,返回空。
32:     return null;
33: }
  • 第 11 行:调用 #findByAppIdAndNamespaceName(appId, namespaceName) 方法,获得 App 下所有的 Namespace 数组。代码如下:

    public List<Namespace> findByAppIdAndNamespaceName(String appId, String namespaceName) {
        return namespaceRepository.findByAppIdAndNamespaceName(appId, namespaceName);
    }
    
  • 第12 至 15 行:若只有一个 Namespace ,说明没有 Namespace 。

  • 第 17 行:调用 ClusterService#findChildClusters(appId, parentClusterName) 方法,获得 Cluster 数组。代码如下:

    /**
     * 获得子 Cluster 数组
     *
     * @param appId App 编号
     * @param parentClusterName Cluster 名字
     * @return 子 Cluster 数组
     */
    public List<Cluster> findChildClusters(String appId, String parentClusterName) {
        // 获得父 Cluster 对象
        Cluster parentCluster = findOne(appId, parentClusterName);
        // 若不存在,抛出 BadRequestException 异常
        if (parentCluster == null) {
            throw new BadRequestException("parent cluster not exist");
        }
        // 获得子 Cluster 数组
        return clusterRepository.findByParentClusterId(parentCluster.getId());
    }
    
  • 第 18 至 21 行:若无 Cluster ,说明没有 Namespace 。

  • 第 23 行:创建 Cluster 的名字的集合。
  • 第 24 至 30 行:遍历 Namespace 数组,若 Namespace 的 Cluster 名字childClusterNames 中,返回该 Namespace 。因为【第 11 行】,获得 App 下所有的 Namespace 数组。

3.2.2 createChildCluster

#createChildCluster(...) 方法,创建 Cluster 对象。代码如下:

private Cluster createChildCluster(String appId, Cluster parentCluster,
                                   String namespaceName, String operator) {
    Cluster childCluster = new Cluster();
    childCluster.setAppId(appId);
    childCluster.setParentClusterId(parentCluster.getId());
    childCluster.setName(UniqueKeyGenerator.generate(appId, parentCluster.getName(), namespaceName));
    childCluster.setDataChangeCreatedBy(operator);
    childCluster.setDataChangeLastModifiedBy(operator);
    return childCluster;
}
  • appId 字段,指向和 Cluster 相同。
  • parentClusterId 字段,指向 Cluster 编号。
  • name 字段,调用 UniqueKeyGenerator#generate(appId, parentClusterName, namespaceName) 方法,创建唯一 KEY 。例如,"20180422134118-dee27ba3456ff928"

3.2.3 createNamespaceBranch

#createNamespaceBranch(...) 方法,创建 Namespace 对象。代码如下:

private Namespace createNamespaceBranch(String appId, String clusterName, String namespaceName, String operator) {
    Namespace childNamespace = new Namespace();
    childNamespace.setAppId(appId);
    childNamespace.setClusterName(clusterName);
    childNamespace.setNamespaceName(namespaceName);
    childNamespace.setDataChangeLastModifiedBy(operator);
    childNamespace.setDataChangeCreatedBy(operator);
    return childNamespace;
}
  • appId 字段,指向和 Namespace 相同。
  • clusterName 字段,指向和 Cluster 编号。
  • namespaceName 字段,和 Namespace 的名字相同。

666. 彩蛋

巧妙~~~

《配置中心 Apollo 源码解析 —— Portal 创建灰度》

    原文作者:java集合源码分析
    原文地址: https://juejin.im/entry/5b37fd8951882574de4f51d2
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞