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接口需要调用 ProcessServicesaveProcessDefinesaveTaskDefinesaveTaskRelationsaveProcessDefinesaveTaskDefine 是并行调用关系,之后调用 saveTaskRelation
  3. update 和 create 在底层走相同的逻辑,工作流通过判断id是否存在,走更新逻辑,任务通过判断是否存在走更新逻辑
  4. 工作流任务关系的创建与删除,首先更新工作流版本(底层调用 processService.saveProcessDefine ),其次更新关系(底层调用 processService.saveTaskRelation
  5. 任务的 create 接口只需调用 processService.saveTaskDefine,进行创建无关联工作流的任务
  6. 任务的 update 接口先 update 任务,再 updateDag,而 delete 先删除操作后 updateDag,updateDag 的操作是调用 ProcessServicesaveProcessDefinesaveTaskRelation

从上可以看出,接口来源的更改 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_logt_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 等众多优秀开源社区的贡献者。如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 [email protected]

微信公众号

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

image.png

猜你喜欢

转载自juejin.im/post/7068058685074833444
DS