目录

  1. UI 界面
  2. Portal 服务
  3. admin 服务
  4. 总结

1. UI 界面

2. Portal 服务

当我们点击上面的发布按钮的时候,调用的当然是 portal 的接口。具体代码如下:

  /*** 全量发布* @param appId SampleApp* @param env DEV* @param clusterName default* @param namespaceName  application* @param branchName 分支/灰度名称* @param deleteBranch true* @param model {"releaseTitle":"20180716220550-gray-release-merge-to-master","releaseComment":"","isEmergencyPublish":false}* @return*/@PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)")@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/merge", method = RequestMethod.POST)public ReleaseDTO merge(@PathVariable String appId, @PathVariable String env,@PathVariable String clusterName, @PathVariable String namespaceName,@PathVariable String branchName, @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,@RequestBody NamespaceReleaseModel model) {// 如果是紧急发布,但该环境不允许紧急发布,抛出异常if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.fromString(env))) {throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));}// 合并主版本和灰度版本, 得到一个发布 dtoReleaseDTO createdRelease = namespaceBranchService.merge(appId, Env.valueOf(env), clusterName, namespaceName, branchName,model.getReleaseTitle(), model.getReleaseComment(),model.isEmergencyPublish(), deleteBranch);ConfigPublishEvent event = ConfigPublishEvent.instance();event.withAppId(appId).withCluster(clusterName).withNamespace(namespaceName).withReleaseId(createdRelease.getId()).setMergeEvent(true).setEnv(Env.valueOf(env));publisher.publishEvent(event);// 发送邮件return createdRelease;}

接口职责不多:是否符合紧急发布的数据校验,调用 Service, 发布“配置发布”事件(发送邮件)。

看看调用 Service 的过程,该方法称为 merge ,实际上就是合并灰度和主版本的配置。代码如下:

  public ReleaseDTO merge(String appId, Env env, String clusterName, String namespaceName,String branchName, String title, String comment,boolean isEmergencyPublish, boolean deleteBranch) {// 计算 changeSetsItemChangeSets changeSets = calculateBranchChangeSet(appId, env, clusterName, namespaceName, branchName);// 调用 admin 服务ReleaseDTO mergedResult =releaseService.updateAndPublish(appId, env, clusterName, namespaceName, title, comment,branchName, isEmergencyPublish, deleteBranch, changeSets);Tracer.logEvent(TracerEventType.MERGE_GRAY_RELEASE,String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));return mergedResult;}

做了 2 件事情: 计算 change 集合,调用 admin 服务。很明显,计算 change 对于 protal 非常重要。

calculateBranchChangeSet 方法主要将灰度配置和主版本配置合并。

代码:

private ItemChangeSets calculateBranchChangeSet(String appId, Env env, String clusterName, String namespaceName,String branchName) {NamespaceBO parentNamespace = namespaceService.loadNamespaceBO(appId, env, clusterName, namespaceName);// 父版本 namespaceif (parentNamespace == null) {throw new BadRequestException("base namespace not existed");}if (parentNamespace.getItemModifiedCnt() > 0) {throw new BadRequestException("Merge operation failed. Because master has modified items");}List<ItemDTO> masterItems = itemService.findItems(appId, env, clusterName, namespaceName);// 主版本 items List<ItemDTO> branchItems = itemService.findItems(appId, env, branchName, namespaceName);// 子版本 items ItemChangeSets changeSets = itemsComparator.compareIgnoreBlankAndCommentItem(parentNamespace.getBaseInfo().getId(),masterItems, branchItems);// 得到 changeSetchangeSets.setDeleteItems(Collections.emptyList());// 防止误删除,emm,灰度的内容并不是全量的,因此上面的计算有些问题,并且目前没有删除功能。所以这里可以置空。changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId());return changeSets;
}

步骤:

  1. 获取主版本的 namespace 详细信息,用于数据检验,id 赋值。
  2. 获取主版本的所有 item 配置,再获取灰度版本的所有 item 配置,注意,灰度版本的 item 只有其自身新增的和修改的配置,不是全量的(这将导致后面一个奇怪的现象)。
  3. 比较两者差异,得到 change 集合。
  4. 设置 deleteList 为空 —— 奇怪现象(灰度的内容并不是全量的,因此上面的计算有些问题,并且目前没有删除功能。所以这里可以置空, 并且防止误删除)。
  5. 设置修改人。

这里需要注意的是计算差异到底是怎么计算的,为什么后面有置空 deleteItem 的操作。

我就不贴全部的方法了,贴一下对删除操作有影响的代码:

/** 比较,忽略空格,返回一个改变的 items */
public ItemChangeSets compareIgnoreBlankAndCommentItem(long baseNamespaceId, List<ItemDTO> baseItems, List<ItemDTO> targetItems){// 忽略新增/修改 item 代码......// 处理删除,但这个逻辑似乎不对. 不过此类不知道数据来源,工具类没有问题.for (ItemDTO item: baseItems){// 主版本String key = item.getKey();ItemDTO targetItem = targetItemMap.get(key);if(targetItem == null){//delete// 如果灰度版本里没有,说明删除了.changeSets.addDeleteItem(item);// 添加进删除集合}}return changeSets;
}

可以看到,这段代码里,循环主版本,逐个对比灰度版本,如果灰度版本里没有,就添加进 delete 集合,而我们知道,灰度版本的 item 只有修改的和新增的,这时,将导致误删除。

但这个工具类的计算是没有问题的,有问题的是外层数据的完整性。

因此需要在外面打个补丁:changeSets.setDeleteItems(Collections.emptyList());

好,计算完 changeSet,就要调用 admin 服务了,并且把 changeSet 传递过去,然后返回一个 release 对象,表示发布成功,并发布事件。

在分析 admin 之前,总结一下 protal 的流程:

3. admin 服务

从 portal 的代码中,可以看到,调用的是 admin 的 updateAndPublish 方法接口,看看这个接口:
位置 : com.ctrip.framework.apollo.adminservice.controller.ReleaseController.java
代码如下:

  @Transactional@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish", method = RequestMethod.POST)public ReleaseDTO updateAndPublish(@PathVariable("appId") String appId,// 应用名称@PathVariable("clusterName") String clusterName,//集群@PathVariable("namespaceName") String namespaceName,// 主版本名称@RequestParam("releaseName") String releaseName, // 发布名称@RequestParam("branchName") String branchName,// 灰度名称 cluster@RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,// 是否删除灰度@RequestParam(name = "releaseComment", required = false) String releaseComment,// 评论@RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,// 是否紧急发布@RequestBody ItemChangeSets changeSets) {// 这个是 portal 发来的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.mergeBranchChangeSetsAndRelease(namespace, branchName, releaseName,releaseComment, isEmergencyPublish, changeSets);// 是否删除分支if (deleteBranch) {namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName,NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());}// 保存发布消息到数据库messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),Topics.APOLLO_RELEASE_TOPIC);return BeanUtils.transfrom(ReleaseDTO.class, release);}

这个接口接受 portal 调用,比较有趣的点是,这里的 changeSet 是 portal 计算的,而不是 admin 自己计算的。

然后,controller 层比较简单,数据校验,调用 Service,发送消息。

当然主要看看 Service。

主要是 releaseService 的 mergeBranchChangeSetsAndRelease 方法,看名字,任务很多:合并分支修改集合,并且发布。

代码如下:

@Transactional
public Release mergeBranchChangeSetsAndRelease(Namespace namespace, String branchName, String releaseName,String releaseComment, boolean isEmergencyPublish,ItemChangeSets changeSets) {// 检查锁checkLock(namespace, isEmergencyPublish, changeSets.getDataChangeLastModifiedBy());/// 更新 itemitemSetService.updateSet(namespace, changeSets);// 找到最新发布的 releaseRelease branchRelease = findLatestActiveRelease(namespace.getAppId(), branchName, namespace.getNamespaceName());// release Idlong branchReleaseId = branchRelease == null ? 0 : branchRelease.getId();// 找到当前 namespace 的所有 Item(刚刚更新的)Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);Map<String, Object> operationContext = Maps.newHashMap();// 构造操作上下文 sourceBranch=灰度名称 baseReleaseId=最新的releaseId isEmergencyPublish=是否紧急发布, 用于构建发布历史operationContext.put(ReleaseOperationContext.SOURCE_BRANCH, branchName);operationContext.put(ReleaseOperationContext.BASE_RELEASE_ID, branchReleaseId);operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);// ReleaseHistory Audit 主版本return masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,changeSets.getDataChangeLastModifiedBy(),// 灰度合并回主分支发布ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER, operationContext);}

代码很简单,步骤:

  1. 检查锁,和普通发布一样,判断修改者和发布者是不是同一个人。
  2. 根据 Portal 传递来的 changeSets 更新 item。
  3. 找到最新发布的 release(构建发布历史的上下文)。
  4. 发布主版本。

其中,updateSet 方法比较重要,要看看他是怎么更新 item 的。

方法很长,总之,就是将 changeSet 的内容保存到主版本的 namespace 下。

@Transactional
public ItemChangeSets updateSet(String appId, String clusterName,String namespaceName, ItemChangeSets changeSet) {//  最后改变数据的人String operator = changeSet.getDataChangeLastModifiedBy();// 改变数据的详细信息ConfigChangeContentBuilder configChangeContentBuilder = new ConfigChangeContentBuilder();// 如果创建了新的if (!CollectionUtils.isEmpty(changeSet.getCreateItems())) {// 循环for (ItemDTO item : changeSet.getCreateItems()) {// 转换Item entity = BeanUtils.transfrom(Item.class, item);entity.setDataChangeCreatedBy(operator);entity.setDataChangeLastModifiedBy(operator);// 保存 item 到数据库Item createdItem = itemService.save(entity);// 保存到 builder createItems List 中configChangeContentBuilder.createItem(createdItem);}// 最后记录审核auditService.audit("ItemSet", null, Audit.OP.INSERT, operator);}// 如果有修改的数据if (!CollectionUtils.isEmpty(changeSet.getUpdateItems())) {for (ItemDTO item : changeSet.getUpdateItems()) {// 转换并寻找Item entity = BeanUtils.transfrom(Item.class, item);Item managedItem = itemService.findOne(entity.getId());// 不存在抛出异常if (managedItem == null) {throw new NotFoundException(String.format("item not found.(key=%s)", entity.getKey()));}// 之前的数据Item beforeUpdateItem = BeanUtils.transfrom(Item.class, managedItem);//protect. only value,comment,lastModifiedBy,lineNum can be modified// 将之前数据内容更新managedItem.setValue(entity.getValue());managedItem.setComment(entity.getComment());managedItem.setLineNum(entity.getLineNum());managedItem.setDataChangeLastModifiedBy(operator);// 更新Item updatedItem = itemService.update(managedItem);// 更新 builder 中 valueconfigChangeContentBuilder.updateItem(beforeUpdateItem, updatedItem);}// 最后审核 itemSetauditService.audit("ItemSet", null, Audit.OP.UPDATE, operator);}// 如果有删除的if (!CollectionUtils.isEmpty(changeSet.getDeleteItems())) {for (ItemDTO item : changeSet.getDeleteItems()) {// 数据库删除Item deletedItem = itemService.delete(item.getId(), operator);// 添加到 builder 中configChangeContentBuilder.deleteItem(deletedItem);}// 审核auditService.audit("ItemSet", null, Audit.OP.DELETE, operator);}// 如果 builder 中有内容if (configChangeContentBuilder.hasContent()){// 创建提交记录createCommit(appId, clusterName, namespaceName,configChangeContentBuilder.build(), // 将 build 变成 json 保存changeSet.getDataChangeLastModifiedBy());}return changeSet;
}

在成功更新 itme 之后,便可以进行最终的发布了,发布很简单,就不展开讲了。

然后看看删除灰度,默认是要删除的。

步骤:

  1. 找到灰度发布的最新 release。
  2. 更新灰度规则,置空灰度规则。
  3. 删除灰度 cluster 和关联的 namespace。置于灰度为什么和 cluster 关联,而不是和 namespace 关联,这是因为最初的 apollo 没有设计灰度,后面加上灰度的时候,为了避免 namespace 大幅修改,就在 cluster 里加入父子逻辑了(咨询过作者)。
  4. 记录发布历史。根据是否 merge 记录是放弃灰度还是合并后删除,方便审计。

发布操作有很多类型,apollo 的常量如下:

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;// 灰度版本合并后删除
}

总结一下 admin 的发布流程:

4. 总结

将 portal 和 admin 组合起来看,下图:

转载于:https://www.cnblogs.com/stateis0/p/9479314.html

Apollo 10 — adminService 全量发布相关推荐

  1. Android开发规范:APP版本发布(全量发布、灰度发布)

    我的新书<Android App开发入门与实战>已于2020年8月由人民邮电出版社出版,欢迎购买.点击进入详情 文章目录 全量发布 灰度发布 欢迎加入Android开发交流QQ群: app ...

  2. 全量发布与灰度发布(APP版本发布)

    目录 全量发布 灰度发布   app版本发布,就是app有新的版本发布,需要给用户安装升级使用.   按照app发布的手段来说,大致可以分为两大类:直接全量发布.先灰度发布再全量发布. 全量发布    ...

  3. update 两个表关联_你真的了解全量表,增量表及拉链表吗?

    1 Mysql数据准备 第一天 9月10号数据 1,待支付,2020-09-10 12:20:11,2020-09-10 12:20:112,待支付,2020-09-10 14:20:11,2020- ...

  4. hive增量表和全量表_你需要了解的全量表,增量表及拉链表

    mysql测试数据准备第一天 9月10号数据 1 2 31,待支付,2020-09-10 12:20:11,2020-09-10 12:20:11 2,待支付,2020-09-10 14:20:11, ...

  5. hive增量表和全量表_你真的了解全量表,增量表及拉链表吗?

    1 Mysql数据准备 第一天 9月10号数据 1,待支付,2020-09-10 12:20:11,2020-09-10 12:20:112,待支付,2020-09-10 14:20:11,2020- ...

  6. 腾讯 QQ 产品已经实现全量上云;中科院计算所发明新编程语言“木兰”;Electron 7.1.9 发布 | 极客头条...

    整理 | 屠敏 快来收听极客头条音频版吧,智能播报由标贝科技提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注 ...

  7. dms mysql定义变量_数据管理DMS:自建MySQL数据库 全量SQL诊断功能发布啦!-阿里云开发者社区...

    MySQL的用户都面临都一个难题,异常或者故障问题难定位,很多时候都靠"猜". 如果比较幸运,异常正在发生,我们还可以获取到会话.引擎状态等信息: 如果没有异常现场,要找到根因,除 ...

  8. solr mysql增量导入_10.Solr4.10.3数据导入(DIH全量增量同步Mysql数据)

    1.创建MySQL数据 create databasesolr;usesolr;DROP TABLE IF EXISTSstudent;CREATE TABLEstudent ( idchar(10) ...

  9. 腾讯 QQ 产品已经实现全量上云;中科院计算所发明新编程语言“木兰”;Electron 7.1.9 发布 | 极客头条

    一分钟速览新闻点! 贵州今年建成华为数据中心.iCloud 数据中心等项目 美团打车:"出租车感谢费"模块已下线,将进一步评估方案 腾讯 QQ 产品已经实现全量上云 华为面向全球发 ...

最新文章

  1. 关于理解Perl的fork函数的一个范例
  2. formal method lecture 11
  3. 北京师范大学计算机应用基础考试,北京师范大学-计算机应用基础作业(一至九全套)...
  4. CCNP课堂练习四:frame-relay traffic-shaping(帧中继流量×××)
  5. typename的作用
  6. 张文宏直播再曝金句:我宁可戴口罩开空调
  7. 利用Adorner制作用于图像裁切的选择框
  8. 函数中结构体指针作为形参修改指针地址
  9. android 自定义绘画,【整理】Android 自定义视图之画图
  10. maven添加mirror_Maven配置setting.xml值Mirror与Repository区别(示例代码)
  11. 用vue-cli搭建vue项目
  12. oracle 时间段加减,ORACLE 时间加减操作
  13. 深蓝学院-视觉SLAM十四讲-第一章作业
  14. python数据汇总_Python,将数据框中的每日数据汇总到每月和每季度
  15. 解决:return _compile(pattern, flags).search(string) TypeError: expected string or buffer
  16. 「云计算」全球最大的5家云计算公司
  17. 使用贝叶斯进行新闻分类
  18. stm32f103c8t6调试-while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET );卡死
  19. python已打开页面获取信息_python+selenium实现自动登录网页获取信息(一)
  20. 20210719-t101-对称二叉树

热门文章

  1. xstream 数字映射不上去_6个做端口映射的步骤,外网访问内网,菜鸟也能做?
  2. 禁止复制粘贴_网页禁止复制粘贴? 教你5招, 绕过网页限制, 免费复制全网内容!...
  3. 三星手机续航测试软件,三星S21系列续航测试简报出炉
  4. 怎么自动备份mysql数据库备份_3步教你一键自动备份mysql数据库的方法
  5. 高考计算机会考基础知识点,2017高考一定会考的46个知识点!
  6. win7计算机管理对话框功能,win7鼠标设置在哪里|win7打开鼠标属性对话框方法
  7. android singleinstance home,Android启动模式之singleinstance的坑
  8. java文件读写 outputstream_java IO文件读写例子(OutputStream,InputStream,Writer,Reader)...
  9. java类成员初始化_简单了解Java类成员初始化顺序
  10. (7) hibernate之级联cascade和关系维持inverse