【Apollo】【二】发布配置的过程-Portal 发布配置
1 前言
上节我们看了下我们服务启动的时候,首次去加载配置的过程,那么这节来开始看看当我们在 Apollo上配置变更的时候,我们的服务又是如何一步一步的热更新掉的呢,也就是实时更新。
2 配置发布过程
我们先了解下,当我们发布一个配置时,Apollo是如何运转的:
上图简要描述了配置发布的大致过程:
- 用户在 Portal 操作配置发布
- Portal 调用 Admin Service 的接口操作发布
- Admin Service 发布配置后,发送 ReleaseMessage (注意这里不是说像MQ似的发消息,而是数据库间隔轮询的)给各个Config Service
- Config Service 收到 ReleaseMessage 后,通知对应的客户端
3 Portal 发布配置
知道了配置的一个变更过程,这节我们先来看看Portal 发布配置:
3.1 相关实体
3.1.1 Release
com.ctrip.framework.apollo.biz.entity.Release
,继承 BaseEntity 抽象类,Release 实体。代码如下:
@Entity @Table(name = "Release") @SQLDelete(sql = "Update Release set isDeleted = 1 where id = ?") // 标记删除 @Where(clause = "isDeleted = 0") public class Release extends BaseEntity { /** * Release Key * * 【TODO 6006】用途? */ @Column(name = "ReleaseKey", nullable = false) private String releaseKey; /** * 标题 */ @Column(name = "Name", nullable = false) private String name; /** * 备注 */ @Column(name = "Comment", nullable = false) private String comment; /** * App 编号 就是我们的服务名称 比如stock-center库存服务 order-center订单服务 */ @Column(name = "AppId", nullable = false) private String appId; /** * Cluster 名字 集群 */ @Column(name = "ClusterName", nullable = false) private String clusterName; /** * Namespace 名字 就是配置比如 application application-datasource application-mybatis等 */ @Column(name = "NamespaceName", nullable = false) private String namespaceName; /** * 配置 Map 字符串,使用 JSON 格式化成字符串 发布时的完整配置 Map 字符串 */ @Column(name = "Configurations", nullable = false) @Lob private String configurations; /** * 是否被回滚(放弃) */ @Column(name = "IsAbandoned", columnDefinition = "Bit default '0'") private boolean isAbandoned; }
3.1.2 ReleaseHistory
com.ctrip.framework.apollo.biz.entity.ReleaseHistory
,继承 BaseEntity 抽象类,ReleaseHistory 实体,记录每次 Release 相关的操作日志。代码如下:
@Entity @Table(name = "ReleaseHistory") @SQLDelete(sql = "Update ReleaseHistory set isDeleted = 1 where id = ?") // 标记删除 @Where(clause = "isDeleted = 0") public class ReleaseHistory extends BaseEntity { /** * App 编号 */ @Column(name = "AppId", nullable = false) private String appId; /** * Cluster 名字 */ @Column(name = "ClusterName", nullable = false) private String clusterName; /** * Namespace 名字 */ @Column(name = "NamespaceName", nullable = false) private String namespaceName; /** * Branch 名 * * 主干,使用 Cluster 名字 * 分支,使用子 Cluster 名字 */ @Column(name = "BranchName", nullable = false) private String branchName; /** * Release 编号 */ @Column(name = "ReleaseId") private long releaseId; /** * 上一次 Release 编号 */ @Column(name = "PreviousReleaseId") private long previousReleaseId; /** * 操作类型 {@link com.ctrip.framework.apollo.common.constants.ReleaseOperation} */ @Column(name = "Operation") private int operation; /** * 操作 Context */ @Column(name = "OperationContext", nullable = false) private String operationContext; }
-
appId
+clusterName
+namespaceName
字段,指向对应的 Namespace 记录。 -
branchName
字段,Branch 名字。- 主干,使用 Cluster 名字。
- 分支,使用子 Cluster 名字。
-
releaseId
字段,Release 编号。 -
previousReleaseId
字段,上一次 Release 编号。 -
operation
类型,操作类型。在com.ctrip.framework.apollo.common.constants.ReleaseOperation
类中,枚举了所有发布相关的操作类型。代码如下:
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; }
3.1.3 ReleaseHistory
com.ctrip.framework.apollo.biz.entity.ReleaseMessage
,不继承 BaseEntity 抽象类,ReleaseMessage 实体。代码如下:
@Entity @Table(name = "ReleaseMessage") public class ReleaseMessage { /** * 编号 */ @Id @GeneratedValue @Column(name = "Id") private long id; /** * 消息内容,通过 {@link com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator#generate(String, String, String)} 方法生成。 */ @Column(name = "Message", nullable = false) private String message; /** * 最后更新时间 */ @Column(name = "DataChange_LastTime") private Date dataChangeLastModifiedTime; @PrePersist protected void prePersist() { if (this.dataChangeLastModifiedTime == null) { dataChangeLastModifiedTime = new Date(); } } }
3.2 发布-Portal门户
3.2.1 ReleaseController
在 apollo-portal
项目中,com.ctrip.framework.apollo.portal.controller.ReleaseController
,提供 Release 的 API 。在【发布】的界面中,点击【 发布 】按钮,调用发布配置的 API 。
#createRelease(appId, env, clusterName, namespaceName, NamespaceReleaseModel)
方法,发布配置。代码如下:
1 @Autowired 2 private ReleaseService releaseService; 3 @Autowired 4 private ApplicationEventPublisher publisher; 5 @Autowired 6 private PortalConfig portalConfig; 7 8 @PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)") 9 @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST) 10 public ReleaseDTO createRelease(@PathVariable String appId, 11 @PathVariable String env, @PathVariable String clusterName, 12 @PathVariable String namespaceName, @RequestBody NamespaceReleaseModel model) { 13 // 校验 NamespaceReleaseModel 非空 14 checkModel(Objects.nonNull(model)); 15 // 设置 PathVariable 变量到 NamespaceReleaseModel 中 16 model.setAppId(appId); 17 model.setEnv(env); 18 model.setClusterName(clusterName); 19 model.setNamespaceName(namespaceName); 20 // 若是紧急发布,但是当前环境未允许该操作,抛出 BadRequestException 异常 21 if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.valueOf(env))) { 22 throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env)); 23 } 24 // 发布配置 25 ReleaseDTO createdRelease = releaseService.publish(model); 26 27 // 创建 ConfigPublishEvent 对象 28 ConfigPublishEvent event = ConfigPublishEvent.instance(); 29 event.withAppId(appId) 30 .withCluster(clusterName) 31 .withNamespace(namespaceName) 32 .withReleaseId(createdRelease.getId()) 33 .setNormalPublishEvent(true) 34 .setEnv(Env.valueOf(env)); 35 // 发布 ConfigPublishEvent 事件 36 publisher.publishEvent(event); 37 38 return createdRelease; 39 }
-
POST
/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases
接口,Request Body 传递 JSON 对象。 -
@PreAuthorize(...)
注解,调用PermissionValidator#hasReleaseNamespacePermissio(appId, namespaceName)
方法,校验是否有发布配置的权限。后续文章,详细分享。 -
com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel
,Namespace 配置发布 Model 。代码如下:
public class NamespaceReleaseModel implements Verifiable { /** * App 编号 */ private String appId; /** * Env 名字 */ private String env; /** * Cluster 名字 */ private String clusterName; /** * Namespace 名字 */ private String namespaceName; /** * 发布标题 */ private String releaseTitle; /** * 发布描述 */ private String releaseComment; /** * 发布人 */ private String releasedBy; /** * 是否紧急发布 */ private boolean isEmergencyPublish; @Override public boolean isInvalid() { return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName, releaseTitle); // 校验非空 } }
-
第 14 行:校验 NamespaceReleaseModel 非空。
-
第 15 至 19 行:设置 PathVariable 变量到 NamespaceReleaseModel 中。
-
第 20 至 23 行:校验若是紧急发布,但是当前环境未允许该操作,抛出 BadRequestException 异常。
- 紧急发布功能,可通过设置 PortalDB 的 ServerConfig 的
"emergencyPublish.supported.envs"
配置开启对应的 Env 们。例如,emergencyPublish.supported.envs = dev
。
- 紧急发布功能,可通过设置 PortalDB 的 ServerConfig 的
-
第 25 行:调用
ReleaseService#publish(NamespaceReleaseModel)
方法,调用 Admin Service API ,发布配置。 -
第 27 至 36 行:创建 ConfigPublishEvent 对象,并调用
ApplicationEventPublisher#publishEvent(event)
方法,发布 ConfigPublishEvent 事件。这部分,我们在后续文章分享。 -
第 38 行:返回 ReleaseDTO 对象。
3.2.2 ReleaseService
在 apollo-portal
项目中,com.ctrip.framework.apollo.portal.service.ReleaseService
,提供 Release 的 Service 逻辑。
#publish(NamespaceReleaseModel)
方法,调用 Admin Service API ,发布配置。代码如下:
@Autowired private UserInfoHolder userInfoHolder; @Autowired private AdminServiceAPI.ReleaseAPI releaseAPI; public ReleaseDTO publish(NamespaceReleaseModel model) { Env env = model.getEnv(); boolean isEmergencyPublish = model.isEmergencyPublish(); String appId = model.getAppId(); String clusterName = model.getClusterName(); String namespaceName = model.getNamespaceName(); String releaseBy = StringUtils.isEmpty(model.getReleasedBy()) ? userInfoHolder.getUser().getUserId() : model.getReleasedBy(); // 调用 Admin Service API ,发布 Namespace 的配置。 ReleaseDTO releaseDTO = releaseAPI.createRelease(appId, env, clusterName, namespaceName, model.getReleaseTitle(), model.getReleaseComment(), releaseBy, isEmergencyPublish); // 【TODO 6001】Tracer 日志 Tracer.logEvent(TracerEventType.RELEASE_NAMESPACE, String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName)); return releaseDTO; }
- 第14 至 17 行:调用
ReleaseAPI#createRelease(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, releaseBy, isEmergencyPublish)
方法,调用 Admin Service API ,发布配置。 - 第 19 行:【TODO 6001】Tracer 日志
3.2.3 ReleaseAPI
com.ctrip.framework.apollo.portal.api.ReleaseAPI
,实现 API 抽象类,封装对 Admin Service 的 Release 模块的 API 调用。代码如下:
3.3 发布-Admin Service
3.3.1 ReleaseController
在 apollo-adminservice
项目中, com.ctrip.framework.apollo.adminservice.controller.ReleaseController
,提供 Release 的 API 。
#publish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, releaseBy, isEmergencyPublish)
方法,发布 Namespace 的配置。代码如下:
1 @Autowired 2 private ReleaseService releaseService; 3 @Autowired 4 private NamespaceService namespaceService; 5 @Autowired 6 private MessageSender messageSender; 7 8 @Transactional 9 @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST) 10 public ReleaseDTO publish(@PathVariable("appId") String appId, 11 @PathVariable("clusterName") String clusterName, 12 @PathVariable("namespaceName") String namespaceName, 13 @RequestParam("name") String releaseName, 14 @RequestParam(name = "comment", required = false) String releaseComment, 15 @RequestParam("operator") String operator, 16 @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) { 17 // 校验对应的 Namespace 对象是否存在。若不存在,抛出 NotFoundException 异常 18 Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName); 19 if (namespace == null) { 20 throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId, clusterName, namespaceName)); 21 } 22 // 发布 Namespace 的配置 23 Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish); 24 25 // send release message 26 // 获得 Cluster 名 27 Namespace parentNamespace = namespaceService.findParentNamespace(namespace); 28 String messageCluster; 29 if (parentNamespace != null) { // 灰度发布 30 messageCluster = parentNamespace.getClusterName(); 31 } else { 32 messageCluster = clusterName; // 使用请求的 ClusterName 33 } 34 // 发送 Release 消息 35 messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName), Topics.APOLLO_RELEASE_TOPIC); 36 37 // 将 Release 转换成 ReleaseDTO 对象 38 return BeanUtils.transfrom(ReleaseDTO.class, release); 39 }
- POST
/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases
接口,Request Body 传递 JSON 对象。 - 第 17 至 21 行:校验对应的 Namespace 对象是否存在。若不存在,抛出 NotFoundException 异常。
- 第 23 行:调用
ReleaseService#publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish)
方法,发布 Namespace 的配置,返回 Release 对象。 - 第 26 至 33 行:获得发布消息的 Cluster 名字。
-
第 27 行:调用
NamespaceService#findParentNamespace(namespace)
方法,获得父 Namespace 对象。代码如下:@Autowired private ClusterService clusterService; @Autowired private ClusterService clusterService; public Namespace findParentNamespace(Namespace namespace) { String appId = namespace.getAppId(); String namespaceName = namespace.getNamespaceName(); // 获得 Cluster Cluster cluster = clusterService.findOne(appId, namespace.getClusterName()); // 若为子 Cluster if (cluster != null && cluster.getParentClusterId() > 0) { // 获得父 Cluster Cluster parentCluster = clusterService.findOne(cluster.getParentClusterId()); // 获得父 Namespace return findOne(appId, parentCluster.getName(), namespaceName); } return null; } public Namespace findOne(String appId, String clusterName, String namespaceName) { return namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName, namespaceName); }
-
-
第 29 至 30 行:若有父 Namespace 对象,说明是子 Namespace ( 灰度发布 ),则使用父 Namespace 的 Cluster 名字。因为,客户端即使在灰度发布的情况下,也是使用 父 Namespace 的 Cluster 名字。也就说,灰度发布,对客户端是透明无感知的。
-
第 32 行:使用请求的 Cluster 名字。
3.3.2 ReleaseService
在 apollo-biz
项目中,com.ctrip.framework.apollo.biz.service.ReleaseService
,提供 Release 的 Service 逻辑给 Admin Service 和 Config Service 。
3.3.2.1 publish
#publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish)
方法,发布 Namespace 的配置。代码如下:
1 private Gson gson = new Gson(); 2 3 @Autowired 4 private ReleaseRepository releaseRepository; 5 @Autowired 6 private ItemService itemService; 7 @Autowired 8 private AuditService auditService; 9 @Autowired 10 private NamespaceLockService namespaceLockService; 11 @Autowired 12 private NamespaceService namespaceService; 13 @Autowired 14 private ReleaseHistoryService releaseHistoryService; 15 16 @Transactional 17 public Release publish(Namespace namespace, String releaseName, String releaseComment, String operator, boolean isEmergencyPublish) { 18 // 校验锁定 19 checkLock(namespace, isEmergencyPublish, operator); 20 // 获得 Namespace 的普通配置 Map 21 Map<String, String> operateNamespaceItems = getNamespaceItems(namespace); 22 // 获得父 Namespace 23 Namespace parentNamespace = namespaceService.findParentNamespace(namespace); 24 // 若有父 Namespace ,则是子 Namespace ,进行灰度发布 25 // branch release 26 if (parentNamespace != null) { 27 return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems, releaseName, releaseComment, operator, isEmergencyPublish); 28 } 29 // 获得子 Namespace 对象 30 Namespace childNamespace = namespaceService.findChildNamespace(namespace); 31 // 获得上一次,并且有效的 Release 对象 32 Release previousRelease = null; 33 if (childNamespace != null) { 34 previousRelease = findLatestActiveRelease(namespace); 35 } 36 // 创建操作 Context 37 // master release 38 Map<String, Object> operationContext = Maps.newHashMap(); 39 operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish); 40 // 主干发布 41 Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, ReleaseOperation.NORMAL_RELEASE, operationContext); // 是否紧急发布。 42 // 若有子 Namespace 时,自动将主干合并到子 Namespace ,并进行一次子 Namespace 的发布 43 // merge to branch and auto release 44 if (childNamespace != null) { 45 mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems, 46 releaseName, releaseComment, operator, previousRelease, 47 release, isEmergencyPublish); 48 } 49 return release; 50 }
-
第 19 行:调用
#checkLock(namespace, isEmergencyPublish, operator)
方法,校验 NamespaceLock 锁定。代码如下:private void checkLock(Namespace namespace, boolean isEmergencyPublish, String operator) { if (!isEmergencyPublish) { // 非紧急发布 // 获得 NamespaceLock 对象 NamespaceLock lock = namespaceLockService.findLock(namespace.getId()); // 校验锁定人是否是当前管理员。若是,抛出 BadRequestException 异常 if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) { throw new BadRequestException("Config can not be published by yourself."); } } }
-
第 21 行:调用
#getNamespaceItems(namespace)
方法,获得 Namespace 的普通配置 Map 。代码如下:private Map<String, String> getNamespaceItems(Namespace namespace) { // 读取 Namespace 的 Item 集合 List<Item> items = itemService.findItemsWithoutOrdered(namespace.getId()); // 生成普通配置 Map 。过滤掉注释和空行的配置项 Map<String, String> configurations = new HashMap<String, String>(); for (Item item : items) { if (StringUtils.isEmpty(item.getKey())) { continue; } configurations.put(item.getKey(), item.getValue()); } return configurations; }
-
第 23 行:调用
#findParentNamespace(namespace)
方法,获得父 Namespace 对象。 -
第 26 至 28 行:若有父 Namespace 对象,灰度发布 。
-
第 30 行:调用
NamespaceService#findChildNamespace(namespace)
方法,获得子 Namespace 对象 。 -
第 31 至 35 行:调用
#findLatestActiveRelease(Namespace)
方法,获得上一次,并且有效的 Release 对象。代码如下:public Release findLatestActiveRelease(Namespace namespace) { return findLatestActiveRelease(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName()); } public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) { return releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId, clusterName, namespaceName); // IsAbandoned = False && Id DESC }
-
第 36 至 39 行:创建操作 Context 。
-
第 41 行:调用
#masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, releaseOperation, operationContext)
方法,主干发布配置。🙂 创建的 Namespace ,默认就是主干,而灰度发布使用的是分支。 -
第 42 至 48 行:调用
#mergeFromMasterAndPublishBranch(...)
方法,若有子 Namespace 时,自动将主干合并到子 Namespace ,并进行一次子 Namespace 的发布。 -
第 49 行:返回 Release 对象 。
3.3.2.2 masterRelease
#masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems, operator, releaseOperation, operationContext)
方法,主干发布配置。代码如下:
1 private Release masterRelease(Namespace namespace, String releaseName, String releaseComment, 2 Map<String, String> configurations, String operator, 3 int releaseOperation, Map<String, Object> operationContext) { 4 // 获得最后有效的 Release 对象 5 Release lastActiveRelease = findLatestActiveRelease(namespace); 6 long previousReleaseId = lastActiveRelease == null ? 0 : lastActiveRelease.getId(); 7 // 创建 Release 对象,并保存 8 Release release = createRelease(namespace, releaseName, releaseComment, configurations, operator); 9 10 // 创建 ReleaseHistory 对象,并保存 11 releaseHistoryService.createReleaseHistory(namespace.getAppId(), namespace.getClusterName(), 12 namespace.getNamespaceName(), namespace.getClusterName(), 13 release.getId(), previousReleaseId, releaseOperation, 14 operationContext, operator); 15 return release; 16 }
-
第 5 行:调用
#findLatestActiveRelease(namespace)
方法,获得最后、有效的 Release 对象。代码如下:public Release findLatestActiveRelease(Namespace namespace) { return findLatestActiveRelease(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName()); } public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) { return releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId, clusterName, namespaceName); // IsAbandoned = False && Id DESC }
-
第 8 行:调用
#createRelease(namespace, releaseName, releaseComment, configurations, operator)
方法,创建 Release 对象,并保存。 -
第10 至 14 行:调用
ReleaseHistoryService#createReleaseHistory(appId, clusterName, namespaceName, branchName, releaseId, previousReleaseId, operation, operationContext, operator)
方法,创建 ReleaseHistory 对象,并保存。
3.3.2.3 createRelease
#createRelease(namespace, releaseName, releaseComment, configurations, operator)
方法,创建 Release 对象,并保存。代码如下:
private Release createRelease(Namespace namespace, String name, String comment, Map<String, String> configurations, String operator) { // 创建 Release 对象 Release release = new Release(); release.setReleaseKey(ReleaseKeyGenerator.generateReleaseKey(namespace)); //【TODO 6006】Release Key 用途? release.setDataChangeCreatedTime(new Date()); release.setDataChangeCreatedBy(operator); release.setDataChangeLastModifiedBy(operator); release.setName(name); release.setComment(comment); release.setAppId(namespace.getAppId()); release.setClusterName(namespace.getClusterName()); release.setNamespaceName(namespace.getNamespaceName()); release.setConfigurations(gson.toJson(configurations)); // 使用 Gson ,将配置 Map 格式化成字符串。 // 保存 Release 对象 release = releaseRepository.save(release); // 释放 NamespaceLock namespaceLockService.unlock(namespace.getId()); // 记录 Audit 到数据库中 auditService.audit(Release.class.getSimpleName(), release.getId(), Audit.OP.INSERT, release.getDataChangeCreatedBy()); return release; }
- 第 4 至 14 行:创建 Release 对象,并设置对应的属性。
- 第 5 行:【TODO 6006】Release Key 用途?
- 第 14 行:调用
Gson#toJson(src)
方法,将配置 Map 格式化成字符串。
- 第 16 行:调用
ReleaseRepository#save(Release)
方法,保存 Release 对象。 - 第 18 行:调用
NamespaceLockService#unlock(namespaceId)
方法,释放 NamespaceLock 。 - 第 20 行:记录 Audit 到数据库中。
3.3.3 ReleaseRepository
com.ctrip.framework.apollo.biz.repository.ReleaseRepository
,继承 org.springframework.data.repository.PagingAndSortingRepository
接口,提供 Release 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface ReleaseRepository extends PagingAndSortingRepository<Release, Long> { Release findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName, @Param("namespaceName") String namespaceName); Release findByIdAndIsAbandonedFalse(long id); List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page); List<Release> findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page); List<Release> findByReleaseKeyIn(Set<String> releaseKey); List<Release> findByIdIn(Set<Long> releaseIds); @Modifying @Query("update Release set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3") int batchDelete(String appId, String clusterName, String namespaceName, String operator); // For release history conversion program, need to delete after conversion it done List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdAsc(String appId, String clusterName, String namespaceName); }
3.3.4 ReleaseHistoryService
在 apollo-biz
项目中,com.ctrip.framework.apollo.biz.service.ReleaseHistoryService
,提供 ReleaseHistory 的 Service 逻辑给 Admin Service 和 Config Service 。
#createReleaseHistory(appId, clusterName, namespaceName, branchName, releaseId, previousReleaseId, operation, operationContext, operator)
方法,创建 ReleaseHistory 对象,并保存。代码如下:
1 private Gson gson = new Gson(); 2 3 @Autowired 4 private ReleaseHistoryRepository releaseHistoryRepository; 5 @Autowired 6 private AuditService auditService; 7 8 @Transactional 9 public ReleaseHistory createReleaseHistory(String appId, String clusterName, String namespaceName, String branchName, 10 long releaseId, long previousReleaseId, int operation, 11 Map<String, Object> operationContext, String operator) { 12 // 创建 ReleaseHistory 对象 13 ReleaseHistory releaseHistory = new ReleaseHistory(); 14 releaseHistory.setAppId(appId); 15 releaseHistory.setClusterName(clusterName); 16 releaseHistory.setNamespaceName(namespaceName); 17 releaseHistory.setBranchName(branchName); 18 releaseHistory.setReleaseId(releaseId); // Release 编号 19 releaseHistory.setPreviousReleaseId(previousReleaseId); // 上一个 Release 编号 20 releaseHistory.setOperation(operation); 21 if (operationContext == null) { 22 releaseHistory.setOperationContext("{}"); //default empty object 23 } else { 24 releaseHistory.setOperationContext(gson.toJson(operationContext)); 25 } 26 releaseHistory.setDataChangeCreatedTime(new Date()); 27 releaseHistory.setDataChangeCreatedBy(operator); 28 releaseHistory.setDataChangeLastModifiedBy(operator); 29 // 保存 ReleaseHistory 对象 30 releaseHistoryRepository.save(releaseHistory); 31 // 记录 Audit 到数据库中 32 auditService.audit(ReleaseHistory.class.getSimpleName(), releaseHistory.getId(), Audit.OP.INSERT, releaseHistory.getDataChangeCreatedBy()); 33 return releaseHistory; 34 }
- 第 12 至 28 行:创建 ReleaseHistory 对象,并设置对应的属性。
- 第 30 行:调用
ReleaseHistoryRepository#save(ReleaseHistory)
方法,保存 ReleaseHistory 对象。 - 第 32 行:记录 Audit 到数据库中。
3.3.5 ReleaseHistoryRepository
com.ctrip.framework.apollo.biz.repository.ReleaseHistoryRepository
,继承 org.springframework.data.repository.PagingAndSortingRepository
接口,提供 ReleaseHistory 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface ReleaseHistoryRepository extends PagingAndSortingRepository<ReleaseHistory, Long> { Page<ReleaseHistory> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable pageable); Page<ReleaseHistory> findByReleaseIdAndOperationOrderByIdDesc(long releaseId, int operation, Pageable pageable); Page<ReleaseHistory> findByPreviousReleaseIdAndOperationOrderByIdDesc(long previousReleaseId, int operation, Pageable pageable); @Modifying @Query("update ReleaseHistory set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3") int batchDelete(String appId, String clusterName, String namespaceName, String operator); }
4 小结
好啦,这节我们简单了解了配置的变更过程以及涉及到的组件,并从源码了解了Portal 和AdminService的发布过程,这节我们看了Admin Service 往数据库里放入了 release 以及 releaseHistory 记录,放releaseMessage 的我们下节看哈,有理解不对的地方欢迎指正哈。