## 目录
- UI 界面
- Portal 服务
- admin 服务
- 总结
1. UI 界面
2. Portal 服务
当我们点击上面的发布按钮的时候,调用的当然是 portal 的接口。具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
@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)); } ReleaseDTO 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 ,实际上就是合并灰度和主版本的配置。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public ReleaseDTO merge(String appId, Env env, String clusterName, String namespaceName, String branchName, String title, String comment, boolean isEmergencyPublish, boolean deleteBranch) { ItemChangeSets changeSets = calculateBranchChangeSet(appId, env, clusterName, namespaceName, branchName); 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 方法主要将灰度配置和主版本配置合并。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private ItemChangeSets calculateBranchChangeSet(String appId, Env env, String clusterName, String namespaceName, String branchName) { NamespaceBO parentNamespace = namespaceService.loadNamespaceBO(appId, env, clusterName, namespaceName);
if (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);
List<ItemDTO> branchItems = itemService.findItems(appId, env, branchName, namespaceName);
ItemChangeSets changeSets = itemsComparator.compareIgnoreBlankAndCommentItem(parentNamespace.getBaseInfo().getId(), masterItems, branchItems); changeSets.setDeleteItems(Collections.emptyList()); changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId()); return changeSets; }
|
步骤:
- 获取主版本的 namespace 详细信息,用于数据检验,id 赋值。
- 获取主版本的所有 item 配置,再获取灰度版本的所有 item 配置,注意,灰度版本的 item 只有其自身新增的和修改的配置,不是全量的(这将导致后面一个奇怪的现象)。
- 比较两者差异,得到 change 集合。
- 设置 deleteList 为空 —— 奇怪现象(
灰度的内容并不是全量的,因此上面的计算有些问题,并且目前没有删除功能。所以这里可以置空, 并且防止误删除
)。
- 设置修改人。
这里需要注意的是计算差异到底是怎么计算的,为什么后面有置空 deleteItem 的操作。
我就不贴全部的方法了,贴一下对删除操作有影响的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public ItemChangeSets compareIgnoreBlankAndCommentItem(long baseNamespaceId, List<ItemDTO> baseItems, List<ItemDTO> targetItems){
for (ItemDTO item: baseItems){ String key = item.getKey();
ItemDTO targetItem = targetItemMap.get(key); if(targetItem == null){ 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
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @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) { 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 方法,看名字,任务很多:合并分支修改集合,并且发布。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Transactional public Release mergeBranchChangeSetsAndRelease(Namespace namespace, String branchName, String releaseName, String releaseComment, boolean isEmergencyPublish, ItemChangeSets changeSets) { checkLock(namespace, isEmergencyPublish, changeSets.getDataChangeLastModifiedBy()); itemSetService.updateSet(namespace, changeSets); Release branchRelease = findLatestActiveRelease(namespace.getAppId(), branchName, namespace .getNamespaceName()); long branchReleaseId = branchRelease == null ? 0 : branchRelease.getId(); Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);
Map<String, Object> operationContext = Maps.newHashMap(); operationContext.put(ReleaseOperationContext.SOURCE_BRANCH, branchName); operationContext.put(ReleaseOperationContext.BASE_RELEASE_ID, branchReleaseId); operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish); return masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, changeSets.getDataChangeLastModifiedBy(), ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER, operationContext);
}
|
代码很简单,步骤:
- 检查锁,和普通发布一样,判断修改者和发布者是不是同一个人。
- 根据 Portal 传递来的 changeSets 更新 item。
- 找到最新发布的 release(构建发布历史的上下文)。
- 发布主版本。
其中,updateSet 方法比较重要,要看看他是怎么更新 item 的。
方法很长,总之,就是将 changeSet 的内容保存到主版本的 namespace 下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| @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 createdItem = itemService.save(entity); 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);
managedItem.setValue(entity.getValue()); managedItem.setComment(entity.getComment()); managedItem.setLineNum(entity.getLineNum()); managedItem.setDataChangeLastModifiedBy(operator); Item updatedItem = itemService.update(managedItem); configChangeContentBuilder.updateItem(beforeUpdateItem, updatedItem);
} auditService.audit("ItemSet", null, Audit.OP.UPDATE, operator); } if (!CollectionUtils.isEmpty(changeSet.getDeleteItems())) { for (ItemDTO item : changeSet.getDeleteItems()) { Item deletedItem = itemService.delete(item.getId(), operator); configChangeContentBuilder.deleteItem(deletedItem); } auditService.audit("ItemSet", null, Audit.OP.DELETE, operator); } if (configChangeContentBuilder.hasContent()){ createCommit(appId, clusterName, namespaceName, configChangeContentBuilder.build(), changeSet.getDataChangeLastModifiedBy()); }
return changeSet; }
|
在成功更新 itme 之后,便可以进行最终的发布了,发布很简单,就不展开讲了。
然后看看删除灰度,默认是要删除的。
步骤:
- 找到灰度发布的最新 release。
- 更新灰度规则,置空灰度规则。
- 删除灰度 cluster 和关联的 namespace。置于灰度为什么和 cluster 关联,而不是和 namespace 关联,这是因为最初的 apollo 没有设计灰度,后面加上灰度的时候,为了避免 namespace 大幅修改,就在 cluster 里加入父子逻辑了(咨询过作者)。
- 记录发布历史。根据是否 merge 记录是放弃灰度还是合并后删除,方便审计。
发布操作有很多类型,apollo 的常量如下:
1 2 3 4 5 6 7 8 9 10 11
| 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 组合起来看,下图: