當咱們點擊上面的發佈按鈕的時候,調用的固然是 portal 的接口。具體代碼以下:java
/** * 全量發佈 * @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)); } // 合併主版本和灰度版本, 獲得一個發佈 dto 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 ,實際上就是合併灰度和主版本的配置。代碼以下:json
public ReleaseDTO merge(String appId, Env env, String clusterName, String namespaceName, String branchName, String title, String comment, boolean isEmergencyPublish, boolean deleteBranch) { // 計算 changeSets ItemChangeSets 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 方法主要將灰度配置和主版本配置合併。app
代碼:工具
private ItemChangeSets calculateBranchChangeSet(String appId, Env env, String clusterName, String namespaceName, String branchName) { NamespaceBO parentNamespace = namespaceService.loadNamespaceBO(appId, env, clusterName, namespaceName);// 父版本 namespace 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);// 主版本 items List<ItemDTO> branchItems = itemService.findItems(appId, env, branchName, namespaceName);// 子版本 items ItemChangeSets changeSets = itemsComparator.compareIgnoreBlankAndCommentItem(parentNamespace.getBaseInfo().getId(), masterItems, branchItems);// 獲得 changeSet changeSets.setDeleteItems(Collections.emptyList());// 防止誤刪除,emm,灰度的內容並非全量的,所以上面的計算有些問題,而且目前沒有刪除功能。因此這裏能夠置空。 changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId()); return changeSets; }
步驟:ui
灰度的內容並非全量的,所以上面的計算有些問題,而且目前沒有刪除功能。因此這裏能夠置空, 而且防止誤刪除
)。這裏須要注意的是計算差別究竟是怎麼計算的,爲何後面有置空 deleteItem 的操做。spa
我就不貼所有的方法了,貼一下對刪除操做有影響的代碼:設計
/** 比較,忽略空格,返回一個改變的 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 只有修改的和新增的,這時,將致使誤刪除。code
但這個工具類的計算是沒有問題的,有問題的是外層數據的完整性。
所以須要在外面打個補丁:changeSets.setDeleteItems(Collections.emptyList());
好,計算完 changeSet,就要調用 admin 服務了,而且把 changeSet 傳遞過去,而後返回一個 release 對象,表示發佈成功,併發布事件。
在分析 admin 以前,總結一下 protal 的流程:
從 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()); /// 更新 item itemSetService.updateSet(namespace, changeSets); // 找到最新發布的 release Release branchRelease = findLatestActiveRelease(namespace.getAppId(), branchName, namespace .getNamespaceName()); // release Id long 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); }
代碼很簡單,步驟:
其中,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 中 value configChangeContentBuilder.updateItem(beforeUpdateItem, updatedItem); } // 最後審覈 itemSet 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); // 添加到 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 以後,即可以進行最終的發佈了,發佈很簡單,就不展開講了。
而後看看刪除灰度,默認是要刪除的。
步驟:
發佈操做有不少類型,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 的發佈流程:
將 portal 和 admin 組合起來看,下圖: