前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Apollo 源码解析 —— Portal 创建灰度

Apollo 源码解析 —— Portal 创建灰度

作者头像
芋道源码
发布2020-05-25 16:25:20
7630
发布2020-05-25 16:25:20
举报
文章被收录于专栏:芋道源码1024芋道源码1024

摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-create-namespace-branch/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 1. 概述
  • 2. Portal
    • 2.1 NamespaceBranchController
    • 2.2 NamespaceBranchService
    • 2.3 NamespaceBranchAPI
  • 3. Admin Service
    • 3.1 NamespaceBranchController
    • 3.2 NamespaceBranchService

1. 概述

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

本文分享 Portal 创建灰度 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:

创建灰度,调用的是创建 Namespace 分支 的 API 。通过创建的子 Namespace ,可以关联其自己定义的 Cluster、Item、Release 等等。关系如下所图所示:

  • 创建 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 右上角的【创建灰度】按钮。

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

灰度版本

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

代码语言:javascript
复制
@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 。代码如下:

代码语言:javascript
复制
  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 调用。代码如下:

NamespaceBranchAPI

3. Admin Service 侧

3.1 NamespaceBranchController

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

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

代码语言:javascript
复制
  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 。代码如下:

代码语言:javascript
复制
  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 对象。代码如下:

代码语言:javascript
复制
public Namespace findBranch(String appId, String parentClusterName, String namespaceName) {
    return namespaceService.findChildNamespace(appId, parentClusterName, namespaceName);
}

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

代码语言:javascript
复制
  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 对象。代码如下:

代码语言:javascript
复制
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 对象。代码如下:

代码语言:javascript
复制
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. 彩蛋

巧妙~~~


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 芋道源码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 概述
  • 2. Portal 侧
    • 2.1 NamespaceBranchController
      • 2.2 NamespaceBranchService
        • 2.3 NamespaceBranchAPI
        • 3. Admin Service 侧
          • 3.1 NamespaceBranchController
            • 3.2 NamespaceBranchService
              • 3.2.1 findBranch
              • 3.2.2 createChildCluster
              • 3.2.3 createNamespaceBranch
          • 666. 彩蛋
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档