DS 版本控制核心原理揭秘

image.png

进勇.png

背景介绍

随着大 JSON 拆分,DS 引入了 version 功能,也就是 workflow 有了版本的概念。当前已发版的2.x含有该功能。版本是通过 Int 类型数字表述,初始化版本为 1,一次变更,版本 +1,界面展示时数字前面拼接一个大写字母 V,对应到界面就如下图所示。

image-20220213150152601

核心原理

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 一定不同

实现架构原理

version_control.drawio

  1. 工作流的 createEmpty 只需调用 processService.saveProcessDefine,进行创建空工作流
  2. 工作流create接口需要调用 ProcessService 中 saveProcessDefine、saveTaskDefine 和 saveTaskRelation。saveProcessDefine 和 saveTaskDefine 是并行调用关系,之后调用 saveTaskRelation
  3. update 和 create 在底层走相同的逻辑,工作流通过判断id是否存在,走更新逻辑,任务通过判断是否存在走更新逻辑
  4. 工作流任务关系的创建与删除,首先更新工作流版本(底层调用 processService.saveProcessDefine ),其次更新关系(底层调用 processService.saveTaskRelation)
  5. 任务的 create 接口只需调用 processService.saveTaskDefine,进行创建无关联工作流的任务
  6. 任务的 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 爆炸式增长,影响查询性能,需要更好的解决方式,比如一键化清除历史版本。

推荐阅读

线程池使用及解析

Java基础-锁核心

一次搜索性能的排查过程和优化效果

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

微信公众号

文章同步发布,政采云技术团队公众号,欢迎关注

image.png