背景介绍
随着大 JSON 拆分,DS 引入了 version 功能,也就是 workflow 有了版本的概念。当前已发版的2.x含有该功能。版本是通过 Int 类型数字表述,初始化版本为 1,一次变更,版本 +1,界面展示时数字前面拼接一个大写字母 V,对应到界面就如下图所示。
核心原理
DS 的核心是 workflow,而 workerflow 由三部分组成,分别是工作流基本信息、任务基本信息、工作流任务关系信息。这三部分每一部分的变更都属于 workflow 的变更,故 workflow 的版本都应增加。
工作流任务关系中没有自己单独的版本,包含的版本有工作流的版本、前置任务的版本、当前任务的版本。且按照当前对工作流和任务的设定,一个任务最多只能关联到一个工作流中,也就是说一个任务要么没有关联到工作流而单独存在,要么只能和一个工作流进行关联。
这就引发了两种操作类型。第一种操作类型仅仅工作流的变更,如修改工作流名称(PorcessDefinition name),此时,需要做变更的是工作流名称、工作流版本,及工作流任务关系中的工作流版本(PorcessDefinitionVersion)而所有任务的版本不做任务变动;另外一种操作类型,工作流中任务的变更,如修改某个任务的名称(taskDefinition name),此时三部分都需要变更,工作流需要增加版本,任务需要修改名称和增加版本,与之对应的工作流关系中也需变更,而该工作流中其他任务无需变动。
实现方式
关键表介绍
表名 | 功能介绍 |
---|---|
t_ds_process_definition | 工作流定义表,也被称之为工作流主表,用于存储工作流基本信息,该表信息一定能在日志表找到相同的记录,同一 code 只能出现一条记录 |
t_ds_process_definition_log | 工作流定义日志表,用于存储变更记录,相同 code 的 version 一定不同 |
t_ds_process_task_relation | 工作流任务关系表,也被称之为工作流任务关系主表,没有自己单独的版本。使用 postTaskCode 和 postTaskVersion 表述当前节点,当任务没有前置节点时,preTaskCode 和 preTaskVersion 为 0。当任务有两个上游时,postTaskCode 和 postTaskVersion 会出现重复;当任务有两个下游时,preTaskCode 和 preTaskVersion 会出现重复 |
t_ds_process_task_relation_log | 工作流任务关系日志表,用于存储变更记录 |
t_ds_task_definition | 任务定义表,也被称之为任务定义主表,用于存储工作流基本信息,该表信息一定能在日志表找到相同的记录,同一 code 只能出现一条记录 |
t_ds_task_definition_log | 任务定义日志表,用于存储变更记录,相同 code 的 version 一定不同 |
实现架构原理
- 工作流的 createEmpty 只需调用
processService.saveProcessDefine
,进行创建空工作流 - 工作流create接口需要调用
ProcessService
中saveProcessDefine
、saveTaskDefine
和saveTaskRelation
。saveProcessDefine
和saveTaskDefine
是并行调用关系,之后调用saveTaskRelation
- update 和 create 在底层走相同的逻辑,工作流通过判断id是否存在,走更新逻辑,任务通过判断是否存在走更新逻辑
- 工作流任务关系的创建与删除,首先更新工作流版本(底层调用
processService.saveProcessDefine
),其次更新关系(底层调用processService.saveTaskRelation
) - 任务的 create 接口只需调用
processService.saveTaskDefine
,进行创建无关联工作流的任务 - 任务的 update 接口先 update 任务,再 updateDag,而 delete 先删除操作后 updateDag,updateDag 的操作是调用
ProcessService
中saveProcessDefine
和saveTaskRelation
。
从上可以看出,接口来源的更改 workflow 的操作,底层统一调用 ProcessService 中这三个方法去更新 DB,这带来的好处是更新数据入口的收敛,方便代码管理,上层与底层的解耦。
关键代码揭秘
ProcessService.saveProcessDefine
// syncDefine --> 是否需要同步工作流定义,这步对应工作流实例保存是否同步工作流定义,如果不同步,可在工作流实例页面重跑工作流,实现测试功能
// isFromProcessDefine -->保存工作流定义来源,如果是工作流实例页面保存,默认工作流是上线状态,否则是下线状态
public int saveProcessDefine(User operator, ProcessDefinition processDefinition, Boolean syncDefine, Boolean isFromProcessDefine) {
ProcessDefinitionLog processDefinitionLog = new ProcessDefinitionLog(processDefinition);
// 查询已有数据的最大版本号,增加的数据默认最大版本号 +1
Integer version = processDefineLogMapper.queryMaxVersionForDefinition(processDefinition.getCode());
int insertVersion = version == null || version == 0 ? Constants.VERSION_FIRST : version + 1;
processDefinitionLog.setVersion(insertVersion);
processDefinitionLog.setReleaseState(isFromProcessDefine ? ReleaseState.OFFLINE : ReleaseState.ONLINE);
processDefinitionLog.setOperator(operator.getId());
processDefinitionLog.setOperateTime(processDefinition.getUpdateTime());
int insertLog = processDefineLogMapper.insert(processDefinitionLog);
int result = 1;
// 如若工作流实例不同步工作流定义,只保存数据到日志表而主表数据无需同步
if (Boolean.TRUE.equals(syncDefine)) {
// 如果是新增 processDefinition 的 id 应该是空,否则是更新
if (0 == processDefinition.getId()) {
result = processDefineMapper.insert(processDefinitionLog);
} else {
processDefinitionLog.setId(processDefinition.getId());
result = processDefineMapper.updateById(processDefinitionLog);
}
}
// 最后结果返回最新版本号,方便保存 relation 使用
return (insertLog & result) > 0 ? insertVersion : 0;
}
ProcessService.saveTaskDefine
public int saveTaskDefine(User operator, long projectCode, List<TaskDefinitionLog> taskDefinitionLogs, Boolean syncDefine) {
Date now = new Date();
// 两个集合,一个暂存新增的 task,另一个暂存更新的 task
List<TaskDefinitionLog> newTaskDefinitionLogs = new ArrayList<>();
List<TaskDefinitionLog> updateTaskDefinitionLogs = new ArrayList<>();
for (TaskDefinitionLog taskDefinitionLog : taskDefinitionLogs) {
taskDefinitionLog.set...
// code 和 version 存在,有可能是更新
if (taskDefinitionLog.getCode() > 0 && taskDefinitionLog.getVersion() > 0) {
// 通过查询表确认是否是更新的 task
TaskDefinitionLog definitionCodeAndVersion = taskDefinitionLogMapper
.queryByDefinitionCodeAndVersion(taskDefinitionLog.getCode(), taskDefinitionLog.getVersion());
if (definitionCodeAndVersion != null) {
// 确认该 task 的关键信息是否有改变,如果没改变,则不操作该 task
if (!taskDefinitionLog.equals(definitionCodeAndVersion)) {
taskDefinitionLog.setUserId(definitionCodeAndVersion.getUserId());
Integer version = taskDefinitionLogMapper.queryMaxVersionForDefinition(taskDefinitionLog.getCode());
taskDefinitionLog.setVersion(version + 1);
taskDefinitionLog.setCreateTime(definitionCodeAndVersion.getCreateTime());
updateTaskDefinitionLogs.add(taskDefinitionLog);
}
continue;
}
}
taskDefinitionLog.set...
if (taskDefinitionLog.getCode() == 0) {
try {
taskDefinitionLog.setCode(CodeGenerateUtils.getInstance().genCode());
} catch (CodeGenerateException e) {
logger.error("Task code get error, ", e);
return Constants.DEFINITION_FAILURE;
}
}
newTaskDefinitionLogs.add(taskDefinitionLog);
}
int insertResult = 0;
int updateResult = 0;
// 更新 task 操作
for (TaskDefinitionLog taskDefinitionToUpdate : updateTaskDefinitionLogs) {
TaskDefinition task = taskDefinitionMapper.queryByCode(taskDefinitionToUpdate.getCode());
if (task == null) {
newTaskDefinitionLogs.add(taskDefinitionToUpdate);
} else {
insertResult += taskDefinitionLogMapper.insert(taskDefinitionToUpdate);
if (Boolean.TRUE.equals(syncDefine)) {
taskDefinitionToUpdate.setId(task.getId());
updateResult += taskDefinitionMapper.updateById(taskDefinitionToUpdate);
} else {
updateResult++;
}
}
}
// 新增 task 操作
if (!newTaskDefinitionLogs.isEmpty()) {
insertResult += taskDefinitionLogMapper.batchInsert(newTaskDefinitionLogs);
if (Boolean.TRUE.equals(syncDefine)) {
updateResult += taskDefinitionMapper.batchInsert(newTaskDefinitionLogs);
} else {
updateResult += newTaskDefinitionLogs.size();
}
}
return (insertResult & updateResult) > 0 ? 1 : Constants.EXIT_CODE_SUCCESS;
}
ProcessService.saveTaskRelation
public int saveTaskRelation(User operator, long projectCode,
long processDefinitionCode, //需要变更 relation 的工作流的 code
int processDefinitionVersion,//需要变更 relation 的工作流的 version
List<ProcessTaskRelationLog> taskRelationList, // 需要变更的 relation
List<TaskDefinitionLog> taskDefinitionLogs,// 所关联的任务定义
Boolean syncDefine) {
if (taskRelationList.isEmpty()) {
return Constants.EXIT_CODE_SUCCESS;
}
Map<Long, TaskDefinitionLog> taskDefinitionLogMap = null;
if (CollectionUtils.isNotEmpty(taskDefinitionLogs)) {
taskDefinitionLogMap = taskDefinitionLogs.stream()
.collect(Collectors.toMap(TaskDefinition::getCode, taskDefinitionLog -> taskDefinitionLog));
}
Date now = new Date();
for (ProcessTaskRelationLog processTaskRelationLog : taskRelationList) {
// 使用最新的 projectCode、processDefinitionCode、processDefinitionVersion
processTaskRelationLog.setProjectCode(projectCode);
processTaskRelationLog.setProcessDefinitionCode(processDefinitionCode);
processTaskRelationLog.setProcessDefinitionVersion(processDefinitionVersion);
// 更新任务的版本号
if (taskDefinitionLogMap != null) {
TaskDefinitionLog preTaskDefinitionLog = taskDefinitionLogMap.get(processTaskRelationLog.getPreTaskCode());
if (preTaskDefinitionLog != null) {
processTaskRelationLog.setPreTaskVersion(preTaskDefinitionLog.getVersion());
}
TaskDefinitionLog postTaskDefinitionLog = taskDefinitionLogMap.get(processTaskRelationLog.getPostTaskCode());
if (postTaskDefinitionLog != null) {
processTaskRelationLog.setPostTaskVersion(postTaskDefinitionLog.getVersion());
}
}
processTaskRelationLog.set...
}
int insert = taskRelationList.size();
if (Boolean.TRUE.equals(syncDefine)) {
// 判断 relation 信息是否与查询出来的一样,如果一样就直接退出
List<ProcessTaskRelation> processTaskRelationList = processTaskRelationMapper.queryByProcessCode(projectCode, processDefinitionCode);
if (!processTaskRelationList.isEmpty()) {
Set<Integer> processTaskRelationSet = processTaskRelationList.stream().map(ProcessTaskRelation::hashCode).collect(toSet());
Set<Integer> taskRelationSet = taskRelationList.stream().map(ProcessTaskRelationLog::hashCode).collect(toSet());
boolean result = CollectionUtils.isEqualCollection(processTaskRelationSet, taskRelationSet);
if (result) {
return Constants.EXIT_CODE_SUCCESS;
}
// 删除当前 relation
processTaskRelationMapper.deleteByCode(projectCode, processDefinitionCode);
}
// 插入最新的 relation
insert = processTaskRelationMapper.batchInsert(taskRelationList);
}
int resultLog = processTaskRelationLogMapper.batchInsert(taskRelationList);
return (insert & resultLog) > 0 ? Constants.EXIT_CODE_SUCCESS : Constants.EXIT_CODE_FAILURE;
}
当前不足
由于工作流基本信息的变更、DAG 中任务的变更及 DAG 中任务关系的变更都会影响工作流 version,所以会出现 t_ds_process_definition_log
和 t_ds_process_task_relation_log
爆炸式增长,影响查询性能,需要更好的解决方式,比如一键化清除历史版本。
推荐阅读
kubernetes scheduler 源码解析及自定义资源调度算法实践
招贤纳士
政采云技术团队(Zero),一个富有激情、创造力和执行力的团队,Base 在风景如画的杭州。团队现有300多名研发小伙伴,既有来自阿里、华为、网易的“老”兵,也有来自浙大、中科大、杭电等校的新人。团队在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com
微信公众号
文章同步发布,政采云技术团队公众号,欢迎关注