1. 概述
2. Portal 侧
- 2.1 NamespaceBranchController
- 2.2 NamespaceBranchService
- 2.3 ReleaseAPI
3. Admin Service 侧
- 3.1 ReleaseController
- 3.2 ReleaseService
- 3.3 NamespaceBranchService
- 3.4 ClusterService
666. 彩蛋
RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
您对于源码的疑问每条留言都将得到认真回复. 甚至不知道如何读源码也可以请教噢.
新的源码解析文章实时收到通知. 每周更新一篇左右.
认真的源码交流微信群.
1. 概述
老艿艿: 本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 https://github.com/ctripcorp/apollo/wiki/ , 特别是 《Apollo 官方 wiki 文档 -- 灰度发布使用指南》.
本文接 《Apollo 源码解析 -- Portal 灰度发布》 , 分享灰度全量发布.
我们先来看看官方文档对灰度全量发布的使用指南, 来理解下它的定义和流程.
如果灰度的配置测试下来比较理想, 符合预期, 那么就可以操作 [全量发布] .
全量发布的效果是:
灰度版本的配置会合并回主版本, 在这个例子中, 就是主版本的 timeout 会被更新成 3000
主版本的配置会自动进行一次发布
在全量发布页面, 可以选择是否保留当前灰度版本, 默认为不保留.
我选择了不保留灰度版本, 所以发布完的效果就是主版本的配置更新, 灰度版本删除. 点击主版本的实例列表, 可以看到 10.32.21.22 和 10.32.21.19 都使用了主版本最新的配置.
灰度全量发布, 和 《Apollo 源码解析 -- Portal 发布配置》 http://www.iocoder.cn/Apollo/portal-publish/?self , 差异点在于, 多了一步配置合并, 所以代码实现上, 有很多相似度. 整体系统流程如下:
2. Portal 侧
2.1 NamespaceBranchController
在 apollo-portal 项目中, com.ctrip.framework.apollo.portal.controller.NamespaceBranchController , 提供 Namespace 分支的 API .
- #merge(...) 方法, 灰度全量发布, 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release . 代码如下:
- @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) {
- // 若是紧急发布, 但是当前环境未允许该操作, 抛出 BadRequestException 异常
- if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.fromString(env))) {
- throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));
- }
- // 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release
- ReleaseDTO createdRelease = namespaceBranchService.merge(appId, Env.valueOf(env), clusterName, namespaceName, branchName,
- model.getReleaseTitle(), model.getReleaseComment(),
- model.isEmergencyPublish(), deleteBranch);
- // 创建 ConfigPublishEvent 对象
- ConfigPublishEvent event = ConfigPublishEvent.instance();
- event.withAppId(appId)
- .withCluster(clusterName)
- .withNamespace(namespaceName)
- .withReleaseId(createdRelease.getId())
- .setMergeEvent(true)
- .setEnv(Env.valueOf(env));
- // 发布 ConfigPublishEvent 事件
- publisher.publishEvent(event);
- return createdRelease;
- }
- POST
- /apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/merge
接口, Request Body 传递 JSON 对象.
@PreAuthorize(...) 注解, 调用
PermissionValidator#hasReleaseNamespacePermissio(appId, namespaceName)
方法, 校验是否有发布配置的权限. 后续文章, 详细分享.
第 7 至 10 行: 校验若是紧急发布, 但是当前环境未允许该操作, 抛出 BadRequestException 异常.
第 11 至 14 行: 调用
NamespaceBranchService#merge(...)
方法, 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release .
第 16 至 25 行: 创建 ConfigPublishEvent 对象, 并调用
ApplicationEventPublisher#publishEvent(event)
方法, 发布 ConfigPublishEvent 事件. 这部分, 我们在后续文章分享.
第 26 行: 返回 ReleaseDTO 对象.
2.2 NamespaceBranchService
在 apollo-portal 项目中, com.ctrip.framework.apollo.portal.service.NamespaceBranchService , 提供 Namespace 分支的 Service 逻辑.
- #merge(...) 方法, 调用 Admin Service API , 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release . 代码如下:
- @Autowired
- private AdminServiceAPI.NamespaceBranchAPI namespaceBranchAPI;
- @Autowired
- private ReleaseService releaseService;
- public ReleaseDTO merge(String appId, Env env, String clusterName, String namespaceName,
- String branchName, String title, String comment,
- boolean isEmergencyPublish, boolean deleteBranch) {
- // 计算变化的 Item 集合
- ItemChangeSets changeSets = calculateBranchChangeSet(appId, env, clusterName, namespaceName, branchName);
- // 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release
- ReleaseDTO mergedResult = releaseService.updateAndPublish(appId, env, clusterName, namespaceName, title, comment,
- branchName, isEmergencyPublish, deleteBranch, changeSets);
- // [TODO 6001] Tracer 日志
- Tracer.logEvent(TracerEventType.MERGE_GRAY_RELEASE, String.format("%s %s %s %s", appId, env, clusterName, namespaceName));
- return mergedResult;
- }
第 10 行: 调用
#calculateBranchChangeSet(appId, env, clusterName, namespaceName, branchName)
方法, 计算变化的 Item 集合. 详细解析, 见 「2.2.1 calculateBranchChangeSet」 .
第 12 至 13 行: 调用 ReleaseService#updateAndPublish(...) 方法, 调用 Admin Service API , 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release . 代码如下:
- @Autowired
- private AdminServiceAPI.ReleaseAPI releaseAPI;
- public ReleaseDTO updateAndPublish(String appId, Env env, String clusterName, String namespaceName,
- String releaseTitle, String releaseComment, String branchName,
- boolean isEmergencyPublish, boolean deleteBranch, ItemChangeSets changeSets) {
- return releaseAPI.updateAndPublish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, branchName,
- isEmergencyPublish, deleteBranch, changeSets);
- }
方法内部, 调用
ReleaseAPI#updateAndPublish(...)
方法, 调用 Admin Service API , 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release . 可能会有胖友会问, 为什么不 NamespaceBranchService 直接调用 ReleaseAPI 呢? ReleaseAPI 属于 ReleaseService 模块, 对外透明, 屏蔽该细节. 这样, 未来 ReleaseService 想要改实现, 可能不是调用 ReleaseAPI 的方法, 而是别的方法, 也是非常方便的.
第 15 行:[TODO 6001] Tracer 日志
- 2.2.1 calculateBranchChangeSet
- @Autowired
- private ItemsComparator itemsComparator;
- @Autowired
- private UserInfoHolder userInfoHolder;
- @Autowired
- private NamespaceService namespaceService;
- @Autowired
- private ItemService itemService;
- private ItemChangeSets calculateBranchChangeSet(String appId, Env env, String clusterName, String namespaceName, String branchName) {
- // 获得父 NamespaceBO 对象
- NamespaceBO parentNamespace = namespaceService.loadNamespaceBO(appId, env, clusterName, namespaceName);
- // 若父 Namespace 不存在, 抛出 BadRequestException 异常.
- if (parentNamespace == null) {
- throw new BadRequestException("base namespace not existed");
- }
- // 若父 Namespace 有配置项的变更, 不允许合并. 因为, 可能存在冲突.
- if (parentNamespace.getItemModifiedCnt()> 0) {
- throw new BadRequestException("Merge operation failed. Because master has modified items");
- }
- // 获得父 Namespace 的 Item 数组
- List<ItemDTO> masterItems = itemService.findItems(appId, env, clusterName, namespaceName);
- // 获得子 Namespace 的 Item 数组
- List<ItemDTO> branchItems = itemService.findItems(appId, env, branchName, namespaceName);
- // 计算变化的 Item 集合
- ItemChangeSets changeSets = itemsComparator.compareIgnoreBlankAndCommentItem(parentNamespace.getBaseInfo().getId(), masterItems, branchItems);
- // 设置 `ItemChangeSets.deleteItem` 为空. 因为子 Namespace 从父 Namespace 继承配置, 但是实际自己没有那些配置项, 所以如果不清空, 会导致这些配置项被删除.
- changeSets.setDeleteItems(Collections.emptyList());
- // 设置 `ItemChangeSets.dataChangeLastModifiedBy` 为当前管理员
- changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId());
- return changeSets;
- }
第 11 至 20 行, 父 Namespace 相关
第 12 行: 调用
namespaceService#loadNamespaceBO(appId, env, clusterName, namespaceName)
方法, 获得父 对象. 该对象, 包含了 Namespace 的详细数据, 包括 Namespace 的基本信息, 配置集合. 详细解析, 点击方法链接查看, 笔者已经添加详细注释. 方法比较冗长, 胖友耐心阅读, 其目的是为了 [第 17 至 20 行] 的判断, 是否有未发布的配置变更.
第 13 至 16 行: 若父 Namespace 不存在, 抛出 BadRequestException 异常.
第 17 至 20 行: 若父 Namespace 有未发布的配置变更, 不允许合并. 因为, 可能存在冲突, 无法自动解决. 此时, 需要在 Portal 上将父 Namespace 的配置进行一次发布, 或者回退回历史版本.
第 21 至 30 行: 获得配置变更集合 ItemChangeSets 对象. 该对象, 我们在 《Apollo 源码解析 -- Portal 批量变更 Item》 .
第 22 行: 调用
ItemService#findItems(appId, env, clusterName, namespaceName)
方法, 获得父 Namespace 的 ItemDTO 数组.
第 24 行: 调用
ItemService#findItems(appId, env, branchName, namespaceName)
方法, 获得子 Namespace 的 ItemDTO 数组.
第 26 行: 调用
ItemsComparator#compareIgnoreBlankAndCommentItem(baseNamespaceId, baseItems, targetItems)
方法, 计算变化的 Item 集合. 详细解析, 点击方法链接查看, 笔者已经添加详细注释.
第 28 行: 设置
ItemChangeSets.deleteItem
为空. 因为子 Namespace 从父 Namespace 继承配置, 但是实际自己没有那些配置项, 所以如果不设置为空, 会导致合并时, 这些配置项被删除.
2.3 ReleaseAPI
com.ctrip.framework.apollo.portal.API.ReleaseAPI , 实现 API 抽象类, 封装对 Admin Service 的 Release 模块的 API 调用. 代码如下:
3. Admin Service 侧
3.1 ReleaseController
在 apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.ReleaseController , 提供 Release 的 API .
- #updateAndPublish(...) 方法, 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release . 代码如下:
- /**
- * merge branch items to master and publish master
- *
- * @return published result
- */
- @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,
- @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch, // 是否删除 Namespace 分支
- @RequestParam(name = "releaseComment", required = false) String releaseComment,
- @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,
- @RequestBody ItemChangeSets changeSets) {
- // 获得 Namespace
- 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));
- }
- // 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release
- Release release = releaseService.mergeBranchChangeSetsAndRelease(namespace, branchName, releaseName, releaseComment, isEmergencyPublish, changeSets);
- // 若需要删除子 Namespace , 则进行删除
- if (deleteBranch) {
- namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName, NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());
- }
- // 发送 Release 消息
- messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName), Topics.APOLLO_RELEASE_TOPIC);
- // 将 Release 转换成 ReleaseDTO 对象
- return BeanUtils.transfrom(ReleaseDTO.class, release);
- }
第 17 至 21 行: 调用
NamespaceService#findOne(ppId, clusterName, namespaceName)
方法, 获得父 Namespace 对象.
若校验到不存在, 抛出 NotFoundException 异常.
第 23 行: 调用
ReleaseService#mergeBranchChangeSetsAndRelease(...)
方法, 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release . 详细解析, 见 「3.2 ReleaseService」 .
第 25 至 27 行: 若需要删除子 Namespace , 即 Portal 中选择 [删除灰度版本] , 调用
NamespaceBranchService#deleteBranch(...)
方法, 删除子 Namespace 相关的记录. 详细解析, 见 「3.3 NamespaceBranchService」 .
第 29 行: 调用
MessageSender#sendMessage(String message, String channel)
方法, 发送发布消息.
第 31 行: 调用
BeanUtils#transfrom(Class<T> clazz, Object src)
方法, 将 Release 转换成 ReleaseDTO 对象.
3.2 ReleaseService
在 apollo-biz 项目中, com.ctrip.framework.apollo.biz.service.ReleaseService , 提供 Release 的 Service 逻辑给 Admin Service 和 Config Service .
- 3.2.1 mergeBranchChangeSetsAndRelease
- ReleaseService#mergeBranchChangeSetsAndRelease(...) 方法, 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release . 代码如下:
- // 合并子 Namespace 变更的配置 Map 到父 Namespace , 并进行一次 Release
- @Transactional
- public Release mergeBranchChangeSetsAndRelease(Namespace namespace, String branchName, String releaseName,
- String releaseComment, boolean isEmergencyPublish,
- ItemChangeSets changeSets) {
- // 校验锁定
- checkLock(namespace, isEmergencyPublish, changeSets.getDataChangeLastModifiedBy());
- // 变更的配置集 合 ItemChangeSets 对象, 更新到父 Namespace 中.
- itemSetService.updateSet(namespace, changeSets);
- // 获得子 Namespace 的最新且有效的 Release 对象
- Release branchRelease = findLatestActiveRelease(namespace.getAppId(), branchName, namespace.getNamespaceName());
- // 获得子 Namespace 的最新且有效的 Release 编号
- long branchReleaseId = branchRelease == null ? 0 : branchRelease.getId();
- // 获得父 Namespace 的配置 Map
- Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);
- // 创建 Map , 用于 ReleaseHistory 对象的 `operationContext` 属性.
- 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);
- // 父 Namespace 进行发布
- return masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
- changeSets.getDataChangeLastModifiedBy(),
- ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER, operationContext);
- }
第 7 行: 调用 #checkLock(...) 方法, 校验锁定.
第 9 行: 调用
ItemService#updateSet(namespace, changeSets)
方法, 将变更的配置集 合 ItemChangeSets 对象, 更新到父 Namespace 中. 详细解析, 在 《Apollo 源码解析 -- Portal 批量变更 Item》 中.
第 17 行: 调用
#getNamespaceItems(namespace)
方法, 获得父 Namespace 的配置 Map . 因为上面已经更新过, 所以获得到的是合并后的结果.
第 11 至 23 行: 创建 Map , 并设置需要的 KV , 用于 ReleaseHistory 对象的 operationContext 属性.
第 12 行: 调用
#findLatestActiveRelease(...)
方法, 获得子 Namespace 的最新且有效的 Release 对象.
第 14 行: 获得子 Namespace 的最新且有效的 Release 编号.
第 21 至 23 行: 设置 KV 到 Map 中.
第 26 至 28 行: 调用
#masterRelease(...)
方法, 父 Namespace 进行发布. 这块, 和 《Apollo 源码解析 -- Portal 发布配置》 http://www.iocoder.cn/Apollo/portal-publish/?self 的逻辑就统一了, 所以详细解析, 见该文.
3.3 NamespaceBranchService
在 apollo-biz 项目中, com.ctrip.framework.apollo.biz.service.NamespaceBranchService , 提供 Namespace 分支的 Service 逻辑给 Admin Service 和 Config Service .
- 3.3.1 deleteBranch
- #deleteBranch(...) 方法, 删除子 Namespace 相关的记录. 代码如下:
- @Transactional
- public void deleteBranch(String appId, String clusterName, String namespaceName,
- String branchName, int branchStatus, String operator) {
- // 获得子 Cluster 对象
- Cluster toDeleteCluster = clusterService.findOne(appId, branchName);
- if (toDeleteCluster == null) {
- return;
- }
- // 获得子 Namespace 的最后有效的 Release 对象
- Release latestBranchRelease = releaseService.findLatestActiveRelease(appId, branchName, namespaceName);
- // 获得子 Namespace 的最后有效的 Release 对象的编号
- long latestBranchReleaseId = latestBranchRelease != null ? latestBranchRelease.getId() : 0;
- // 创建新的, 用于表示删除的 GrayReleaseRule 的对象
- // update branch rules
- GrayReleaseRule deleteRule = new GrayReleaseRule();
- deleteRule.setRules("[]");
- deleteRule.setAppId(appId);
- deleteRule.setClusterName(clusterName);
- deleteRule.setNamespaceName(namespaceName);
- deleteRule.setBranchName(branchName);
- deleteRule.setBranchStatus(branchStatus); // Namespace 分支状态
- deleteRule.setDataChangeLastModifiedBy(operator);
- deleteRule.setDataChangeCreatedBy(operator);
- // 更新 GrayReleaseRule
- doUpdateBranchGrayRules(appId, clusterName, namespaceName, branchName, deleteRule, false, -1);
- // 删除子 Cluster
- // delete branch cluster
- clusterService.delete(toDeleteCluster.getId(), operator);
- // 创建 ReleaseHistory 对象, 并保存
- int releaseOperation = branchStatus == NamespaceBranchStatus.MERGED ? ReleaseOperation.GRAY_RELEASE_DELETED_AFTER_MERGE : ReleaseOperation.ABANDON_GRAY_RELEASE;
- releaseHistoryService.createReleaseHistory(appId, clusterName, namespaceName, branchName, latestBranchReleaseId, latestBranchReleaseId,
- releaseOperation, null, operator);
- // 记录 Audit 到数据库中
- auditService.audit("Branch", toDeleteCluster.getId(), Audit.OP.DELETE, operator);
- }
第 4 至 8 行: 调用
ClusterService#findOne(appId, branchName)
方法, 获得子 Cluster 对象.
第 10 行: 调用
ReleaseService#findLatestActiveRelease(namespace)
方法, 获得最后, 有效的 Release 对象.
第 12 行: 获得最后, 有效的 Release 对象的编号.
第 14 至 24 行: 创建新的, 用于表示删除的 GrayReleaseRule 的对象. 并且, 当前场景, 该 GrayReleaseRule 的 branchStatus 为 MERGED .
第 26 行: 调用
#doUpdateBranchGrayRules(...)
方法, 更新 GrayReleaseRule . 详细解析, 见 《Apollo 源码解析 -- Portal 配置灰度规则》 中.
第 30 行: 调用
ClusterService#delte(id, operator)
方法, 删除子 Cluster 相关. 详细解析, 见 「3.4 ClusterService」 .
第 32 至 35 行: 调用
ReleaseHistoryService#createReleaseHistory(...)
方法, 创建 ReleaseHistory 对象, 并保存.
第 37 行: 记录 Audit 到数据库中.
3.4 ClusterService
在 apollo-biz 项目中, com.ctrip.framework.apollo.biz.service.ClusterService , 提供 Cluster 的 Service 逻辑给 Admin Service 和 Config Service .
- 3.4.1 delete
- #delete(...) 方法, 删除 Cluster 相关. 代码如下:
- @Transactional
- public void delete(long id, String operator) {
- // 获得 Cluster 对象
- Cluster cluster = clusterRepository.findOne(id);
- if (cluster == null) {
- throw new BadRequestException("cluster not exist");
- }
- // 删除 Namespace
- // delete linked namespaces
- namespaceService.deleteByAppIdAndClusterName(cluster.getAppId(), cluster.getName(), operator);
- // 标记删除 Cluster
- cluster.setDeleted(true);
- cluster.setDataChangeLastModifiedBy(operator);
- clusterRepository.save(cluster);
- // 记录 Audit 到数据库中
- auditService.audit(Cluster.class.getSimpleName(), id, Audit.OP.DELETE, operator);
- }
会标记删除 Cluster 和其相关的 Namespace . 代码比较简单, 胖友自己看看哈.
666. 彩蛋
灰度发布结束~ 还有一些其他流程, 胖友可以自己看看, 例如放弃灰度.
来源: https://juejin.im/entry/5c86da86f265da2db5427018