老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Apollo 官方 wiki 文档 —— 灰度发布使用指南》。
本文分享 Portal 配置灰度规则 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:
在 apollo-common
项目中,com.ctrip.framework.apollo.common.entity.GrayReleaseRule
,继承 BaseEntity 抽象类,GrayReleaseRule 实体。代码如下:
@Entity
@Table(name = "GrayReleaseRule")
@SQLDelete(sql = "Update GrayReleaseRule set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class GrayReleaseRule 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 名字
*/
@Column(name = "BranchName", nullable = false)
private String branchName;
/**
* 规则,目前将 {@link com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO} 的数组,JSON 格式化
*/
@Column(name = "Rules")
private String rules;
/**
* Release 编号。
*
* 有两种情况:
* 1、当灰度已经发布,则指向对应的最新的 Release 对象的编号
* 2、当灰度还未发布,等于 0 。等到灰度发布后,更新为对应的 Release 对象的编号
*/
@Column(name = "releaseId", nullable = false)
private Long releaseId;
/**
* 分支状态,在 {@link com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus} 枚举
*/
@Column(name = "BranchStatus", nullable = false)
private int branchStatus;
}
appId
+ clusterName
+ namespaceName
+ branchName
四个字段,指向对应的子 Namespace 对象。rules
字段,规则数组,目前将 GrayReleaseRuleItemDTO 数组,JSON 格式化进行存储。详细解析,见 「2.1 GrayReleaseRuleItemDTO」 。字段存储例子如下:
[{"clientAppId":"233","clientIpList":["10.12.13.14","20.23.12.15"]}]release
字段,Release 编号。目前有两种情况:branchStatus
字段,Namespace 分支状态。在 com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus
中,枚举如下:
public interface NamespaceBranchStatus { /** * 删除 */ int DELETED = 0; /** * 激活(有效) */ int ACTIVE = 1; /** * 合并 */ int MERGED = 2; }com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO
,GrayRelease 规则项 DTO 。代码如下:
public class GrayReleaseRuleItemDTO {
public static final String ALL_IP = "*";
/**
* 客户端 App 编号
*/
private String clientAppId;
/**
* 客户端 IP 集合
*/
private Set<String> clientIpList;
// 匹配方法 BEGIN
public boolean matches(String clientAppId, String clientIp) {
return appIdMatches(clientAppId) && ipMatches(clientIp);
}
private boolean appIdMatches(String clientAppId) {
return this.clientAppId.equals(clientAppId);
}
private boolean ipMatches(String clientIp) {
return this.clientIpList.contains(ALL_IP) || clientIpList.contains(clientIp);
}
// 匹配方法 END
}
clientAppId
字段呢?对于公共 Namespace 的灰度规则,需要先指定要灰度的 appId ,然后再选择 IP 。如下图:在 apollo-portal
项目中,com.ctrip.framework.apollo.portal.controller.NamespaceBranchController
,提供 Namespace 分支的 API 。
切换到灰度规则 Tab ,点击【新增规则】按钮。
新增规则在弹出框中【灰度的 IP】下拉框会默认展示当前使用配置的机器列表,选择我们要灰度的 IP,点击完成。
如果下拉框中没找到需要的IP,说明机器还没从Apollo取过配置,可以点击手动输入IP来输入,输入完后点击添加按钮
#updateBranchRules(...)
方法, 更新 Namespace 分支的灰度规则。代码如下:
@PreAuthorize(value = "@permissionValidator.hasOperateNamespacePermission(#appId, #namespaceName)")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules", method = RequestMethod.PUT)
public void updateBranchRules(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@PathVariable String branchName, @RequestBody GrayReleaseRuleDTO rules) {
namespaceBranchService.updateBranchGrayRules(appId, Env.valueOf(env), clusterName, namespaceName, branchName, rules);
}
"apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules
接口 。@PreAuthorize(...)
注解,调用 PermissionValidator#hasOperateNamespacePermission(appId, namespaceName)
方法,校验是否有操作 Namespace 的权限。后续文章,详细分享。com.ctrip.framework.apollo.common.dto.GrayReleaseRuleDTO
,灰度发布规则 DTO ,代码如下:
public class GrayReleaseRuleDTO extends BaseDTO { /** * App 编号 */ private String appId; /** * Cluster 名字 */ private String clusterName; /** * Namespace 名字 */ private String namespaceName; /** * Branch 名字 */ private String branchName; /** * GrayReleaseRuleItemDTO 数组 */ private Set<GrayReleaseRuleItemDTO> ruleItems; /** * Release 编号 * * 更新灰度发布规则时,该参数不会传递 */ private Long releaseId; }NamespaceBranchService#updateBranchGrayRules(...)
方法,更新 Namespace 分支的灰度规则。在 apollo-portal
项目中,com.ctrip.framework.apollo.portal.service.NamespaceBranchService
,提供 Namespace 分支的 Service 逻辑。
#createItem(appId, env, clusterName, namespaceName, ItemDTO)
方法,创建并保存 Item 到 Admin Service 。代码如下:
1: @Autowired
2: private UserInfoHolder userInfoHolder;
3: @Autowired
4: private AdminServiceAPI.NamespaceBranchAPI namespaceBranchAPI;
5:
6: public void updateBranchGrayRules(String appId, Env env, String clusterName, String namespaceName,
7: String branchName, GrayReleaseRuleDTO rules) {
8: // 设置 GrayReleaseRuleDTO 的创建和修改人为当前管理员
9: String operator = userInfoHolder.getUser().getUserId();
10: rules.setDataChangeCreatedBy(operator);
11: rules.setDataChangeLastModifiedBy(operator);
12: // 更新 Namespace 分支的灰度规则
13: namespaceBranchAPI.updateBranchGrayRules(appId, env, clusterName, namespaceName, branchName, rules);
14: // 【TODO 6001】Tracer 日志
15: Tracer.logEvent(TracerEventType.UPDATE_GRAY_RELEASE_RULE, String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
16: }
NamespaceBranchAPI#updateBranchGrayRules(...)
方法,更新 Namespace 分支的灰度规则。com.ctrip.framework.apollo.portal.api.NamespaceBranchAPI
,实现 API 抽象类,封装对 Admin Service 的 Namespace 分支模块的 API 调用。代码如下:
NamespaceBranchAPI
在 apollo-adminservice
项目中, com.ctrip.framework.apollo.adminservice.controller.NamespaceBranchController
,提供 Namespace 分支的 API 。
#updateBranchGrayRules(...)
方法,更新 Namespace 分支的灰度规则。代码如下:
1: @Autowired
2: private MessageSender messageSender;
3: @Autowired
4: private NamespaceBranchService namespaceBranchService;
5: @Autowired
6: private NamespaceService namespaceService;
7:
8: @Transactional
9: @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules", method = RequestMethod.PUT)
10: public void updateBranchGrayRules(@PathVariable String appId, @PathVariable String clusterName,
11: @PathVariable String namespaceName, @PathVariable String branchName,
12: @RequestBody GrayReleaseRuleDTO newRuleDto) {
13: // 校验子 Namespace
14: checkBranch(appId, clusterName, namespaceName, branchName);
15:
16: // 将 GrayReleaseRuleDTO 转成 GrayReleaseRule 对象
17: GrayReleaseRule newRules = BeanUtils.transfrom(GrayReleaseRule.class, newRuleDto);
18: // JSON 化规则为字符串,并设置到 GrayReleaseRule 对象中
19: newRules.setRules(GrayReleaseRuleItemTransformer.batchTransformToJSON(newRuleDto.getRuleItems()));
20: // 设置 GrayReleaseRule 对象的 `branchStatus` 为 ACTIVE
21: newRules.setBranchStatus(NamespaceBranchStatus.ACTIVE);
22: // 更新子 Namespace 的灰度发布规则
23: namespaceBranchService.updateBranchGrayRules(appId, clusterName, namespaceName, branchName, newRules);
24: // 发送 Release 消息
25: messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName), Topics.APOLLO_RELEASE_TOPIC);
26: }
#checkBranch(appId, clusterName, namespaceName, branchName)
,校验子 Namespace 是否存在。代码如下:
private void checkBranch(String appId, String clusterName, String namespaceName, String branchName) { // 校验 Namespace 是否存在 // 1. check parent namespace checkNamespace(appId, clusterName, namespaceName); // 校验子 Namespace 是否存在。若不存在,抛出 BadRequestException 异常 // 2. check child namespace Namespace childNamespace = namespaceService.findOne(appId, branchName, namespaceName); if (childNamespace == null) { throw new BadRequestException(String.format("Namespace's branch not exist. AppId = %s, ClusterName = %s, " + "NamespaceName = %s, BranchName = %s", appId, clusterName, namespaceName, branchName)); } }BeanUtils#transfrom(Class<T> clazz, Object src)
方法,将 GrayReleaseRuleDTO 转换成 GrayReleaseRule 对象。GrayReleaseRuleItemTransformer#batchTransformToJSON(et<GrayReleaseRuleItemDTO> ruleItems)
方法,JSON 化规则为字符串,并设置到 GrayReleaseRule 对象中。代码如下:
private static final Gson gson = new Gson(); public static String batchTransformToJSON(Set<GrayReleaseRuleItemDTO> ruleItems) { return gson.toJson(ruleItems); }branchStatus
为 ACTIVE 。NamespaceBranchService#updateBranchGrayRules(appId, clusterName, namespaceName, branchName, newRules)
方法,更新子 Namespace 的灰度发布规则。详细解析,见 「3.2 NamespaceBranchService」 。MessageSender#sendMessage(message, channel)
方法,发送 Release 消息,从而通知客户端更新配置。在 apollo-biz
项目中,com.ctrip.framework.apollo.biz.service.NamespaceBranchService
,提供 Namespace 分支的 Service 逻辑给 Admin Service 和 Config Service 。
#updateBranchGrayRules(appId, clusterName, namespaceName, branchName, newRules)
方法,更新子 Namespace 的灰度发布规则。代码如下:
1: @Autowired
2: private GrayReleaseRuleRepository grayReleaseRuleRepository;
3: @Autowired
4: private ClusterService clusterService;
5: @Autowired
6: private ReleaseService releaseService;
7: @Autowired
8: private NamespaceService namespaceService;
9: @Autowired
10: private ReleaseHistoryService releaseHistoryService;
11:
12: private void doUpdateBranchGrayRules(String appId, String clusterName, String namespaceName,
13: String branchName, GrayReleaseRule newRules, boolean recordReleaseHistory, int releaseOperation) {
14: // 获得子 Namespace 的灰度发布规则
15: GrayReleaseRule oldRules = grayReleaseRuleRepository.findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(appId, clusterName, namespaceName, branchName);
16: // 获得最新的子 Namespace 的 Release 对象
17: Release latestBranchRelease = releaseService.findLatestActiveRelease(appId, branchName, namespaceName);
18: // 获得最新的子 Namespace 的 Release 对象的编号
19: long latestBranchReleaseId = latestBranchRelease != null ? latestBranchRelease.getId() : 0;
20: // 设置 GrayReleaseRule 的 `releaseId`
21: newRules.setReleaseId(latestBranchReleaseId);
22: // 保存新的 GrayReleaseRule 对象
23: grayReleaseRuleRepository.save(newRules);
24:
25: // 删除老的 GrayReleaseRule 对象
26: // delete old rules
27: if (oldRules != null) {
28: grayReleaseRuleRepository.delete(oldRules);
29: }
30:
31: // 若需要,创建 ReleaseHistory 对象,并保存
32: if (recordReleaseHistory) {
33: Map<String, Object> releaseOperationContext = Maps.newHashMap();
34: releaseOperationContext.put(ReleaseOperationContext.RULES, GrayReleaseRuleItemTransformer.batchTransformFromJSON(newRules.getRules())); // 新规则
35: if (oldRules != null) {
36: releaseOperationContext.put(ReleaseOperationContext.OLD_RULES, GrayReleaseRuleItemTransformer.batchTransformFromJSON(oldRules.getRules())); // 老规则
37: }
38: releaseHistoryService.createReleaseHistory(appId, clusterName, namespaceName, branchName, latestBranchReleaseId,
39: latestBranchReleaseId, releaseOperation, releaseOperationContext, newRules.getDataChangeLastModifiedBy());
40: }
41: }
GrayReleaseRuleRepository#findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(appId, clusterName, namespaceName, branchName)
方法,获得子 Namespace 的灰度发布规则。ReleaseService#findLatestActiveRelease(appId, branchName, namespaceName)
方法,获得最新的,并且有效的,子 Namespace 的 Release 对象。releaseId
属性。GrayReleaseRuleRepository#save(GrayReleaseRule)
方法,保存新的 GrayReleaseRule 对象。ReleaseHistoryService#createReleaseHistory(...)
方法,创建 ReleaseHistory 对象,并保存。其中,ReleaseHistory.operation
属性,为 APPLY_GRAY_RULES 。com.ctrip.framework.apollo.biz.repository.GrayReleaseRuleRepository
,继承 org.springframework.data.repository.PagingAndSortingRepository
接口,提供 GrayReleaseRule 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface GrayReleaseRuleRepository extends PagingAndSortingRepository<GrayReleaseRule, Long> {
GrayReleaseRule findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(String appId, String clusterName, String namespaceName, String branchName);
List<GrayReleaseRule> findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName, String namespaceName);
List<GrayReleaseRule> findFirst500ByIdGreaterThanOrderByIdAsc(Long id);
}