大数据dolphinscheduler的优化实践

一、背景

DolphinScheduler(海豚调度器)是一款开源的分布式调度系统,旨在解决大数据场景下复杂的任务调度和流程编排问题。作为一种可靠、易用且高效的调度工具,DolphinScheduler 提供了强大的任务调度、工作流管理和监控功能,帮助用户实现任务的自动化调度和管理,提高工作效率和数据处理能力。当前大数据生产环境中95%以上的任务通过dolphinscheduler实现协调调度,因此在保障组件的稳定运行和提升组件的使用效率上进行了探索实践。本文将主要介绍当前大数据团队对DS改进实践、监控体系和效率工具。

Open-Falcon是一款开源的数据采集与监控系统,旨在帮助用户监控大规模分布式系统,目前作为大数据dolphinscheduler的告警对接系统,实现监控信息对接钉钉群。

参考文档:

dolphinscheduler.apache.org/zh-cn/docs/...

hitripod.gitbooks.io/open-falcon...

二、Ds改进实践

2.1依赖机制修改

2.1.1 依赖信息介绍

dolphinscheduler不单单支持 DAG 简单的前驱和后继节点之间的依赖关系,同时还提供任务依赖节点,支持流程间的自定义任务依赖。

名词解释:

DAG: 全称 Directed Acyclic Graph,简称 DAG。工作流中的 Task 任务以有向无环图的形式组装起来,从入度为零的节点进行拓扑遍历,直到无后继节点为止。举例如下图:

流程定义 :通过拖拽任务节点并建立任务节点的关联所形成的可视化DAG

流程实例:流程实例是流程定义的实例化,可以通过手动启动或定时调度生成。每运行一次流程定义,产生一个流程实例

任务实例:任务实例是流程定义中任务节点的实例化,标识着某个具体的任务

任务类型 :目前支持有 SHELL、SQL、SUB_PROCESS(子流程)、PROCEDURE、MR、SPARK、PYTHON、DEPENDENT(依赖),同时计划支持动态插件扩展,注意:其中 SUB_PROCESS类型的任务需要关联另外一个流程定义,被关联的流程定义是可以单独启动执行的

2.1.2 问题描述

ds的原生依赖机制是:从元数据库t_ds_process_instance(流程实例表)根据依赖的时间周期(如图示)在其范围内根据工作流的结束时间倒序取第一条工作流实例进行判断。

这就导致了问题:工作流中出现执行失败的节点就需要将完整工作流进行修复,存在已经成功执行占用资源较大、执行时间较长的节点需要重新执行、在包含大量节点的工作流已经执行大半,受影响的只是少量的工作流要重新执行的情况。

但如果只执行失败和未执行的节点,就会导致再失败工作流中已经执行成功的节点在后续的依赖判断中会被判失败。

2.1.3 改进逻辑

在获取新工作流实例的位置增加部分逻辑:获取依赖的节点code,从元数据库t_ds_task_instance表根据依赖的时间周期在其范围内根据工作流的结束时间倒序取第一条节点实例。

此改动既保证了原生逻辑中判断会遵循工作流级(process)实例的完成顺序,又增加节点级别(task)实例的判断。

2.1.4 代码修改

1.dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/utils/DependentExecute.java添加代码:

ini 复制代码
// 代码121行
    result = getDependTaskResult(dependentItem.getDepTaskCode(), processInstance, dateInterval);
      
      
    //函数getDependTaskResult 修改功能:在取最新的流程实例获取对应任务实例依赖为空的情况下,增加单独的任务实例获取
    private DependResult getDependTaskResult(long taskCode, ProcessInstance processInstance, DateInterval dateInterval) {
        DependResult result;
        TaskInstance taskInstance = null;
        List<TaskInstance> taskInstanceList = processService.findValidTaskListByProcessId(processInstance.getId());
      
        for (TaskInstance task : taskInstanceList) {
            if (task.getTaskCode() == taskCode) {
                taskInstance = task;
                break;
            }
        }
      
        if (taskInstance == null) {
            // cannot find task in the process instance
            // maybe because process instance is running or failed.
            if (processInstance.getState().typeIsFinished()) {
                Integer processDefinitionId = processInstance.getId();
                Date taskStartTime = dateInterval.getStartTime();
                Date taskEndTime = dateInterval.getEndTime();
                TaskInstance lastTaskInstance = processService.findLastRunningTaskByProcessDefinitionId(processDefinitionId, taskCode, taskStartTime, taskEndTime);
                if(lastTaskInstance == null) {
                    return DependResult.FAILED;
                }
                if(lastTaskInstance.getState().typeIsFinished()){
                    result = getDependResultByState(lastTaskInstance.getState());
                }else {
                    result = DependResult.WAITING;
                }
            }else{
                return DependResult.WAITING;
            }
        }else{
            result = getDependResultByState(taskInstance.getState());
        }
        return result;
    }

2.dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskInstanceMapper.xml 添加代码:

根据任务实例的开始时间倒序取最新一条数据

xml 复制代码
<select id="findLastRunningTaskByProcessDefinitionId" resultType="org.apache.dolphinscheduler.dao.entity.TaskInstance">
    select *
    from t_ds_task_instance
    <where>
        task_code=#{taskCode}
        <if test="startTime!=null and endTime != null ">
            and start_time <![CDATA[ >= ]]> #{startTime} and start_time <![CDATA[ <= ]]> #{endTime}
        </if>
    </where>
    order by start_time desc limit 1
</select>

3.dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/TaskInstanceMapper.java添加代码:

less 复制代码
TaskInstance findLastRunningTaskByProcessDefinitionId(@Param("processDefinitionId") Integer processDefinitionId,
                                                      @Param("states") int[] stateArray,
                                                      @Param("taskCode") long taskCode,
                                                      @Param("startTime") Date startTime,
                                                      @Param("endTime") Date endTime
);

4.dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessService.java 添加代码:

vbnet 复制代码
TaskInstance findLastRunningTaskByProcessDefinitionId(Integer processDefinitionId, long taskCode, Date startTime, Date endTime);

5.dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessServiceImpl.java 添加代码:

scss 复制代码
private final int[] stateArray = new int[]{ExecutionStatus.Pending.ordinal(),
ExecutionStatus.InProgress.ordinal(),
ExecutionStatus.Stopping.ordinal(),
ExecutionStatus.Failed.ordinal(),
ExecutionStatus.Stopped.ordinal(),
ExecutionStatus.CompletedWithViolations.ordinal(),
ExecutionStatus.Completed.ordinal()};

 
@Override
public TaskInstance findLastRunningTaskByProcessDefinitionId(Integer processDefinitionId, long taskCode, Date startTime, Date endTime) {
    return taskInstanceMapper.findLastRunningTaskByProcessDefinitionId(processDefinitionId, stateArray, taskCode, startTime, endTime);
}

2.2告警对接open-falcon

2.2.1 问题描述

Ds原生的告警通知如下:

存在以下问题:

1、报警信息不清晰:上报较多无用信息(如code、owner、host及日志信息),导致关键信息淹没

2、没有告警优先级:所有工作流上报信息都一样,某些需要立即关注的问题不能及时感知

3、没有未恢复告警提示:告警信息较多的情况,容易遗漏修复

2.2.2 解决逻辑

确认原生告警逻辑的查询条件:每分钟查询元数据库t_ds_process_instance表,汇总当前分钟内执行结束的工作流信息,并标记对应状态,将获取数据上报open-falcon,实现告警信息自定义配置、告警等级设定、未恢复告警提示。

2.2.3 实现

确认只保留工作流级别的失败通报,通过脚本实现:每分钟获取上一分钟执行结束的工作流实例信息,获取结束状态向falcon上报组装获取的工作流相关信息、指定的告警等级等。

实现逻辑如下:

1、获取监控时间段内的,工作流信息,获取工作流的sql实现:

csharp 复制代码
select pi.id, pd.name as process_name, pi.state
from (select id, state, process_definition_code from t_ds_process_instance where end_time >= '%s'
and end_time < '%s' and (command_param not like '%%parentProcessInstanceId%%' or command_param is null)) pi,
t_ds_process_definition pd
where pi.process_definition_code = pd.code

2、过滤不是结束状态的工作流;

3、对执行结束的工作流进行状态标记;

4、数据上报falcon、设置告警等级、告警过滤、实现告警分情况上报不同群

实现后告警信息如下图:

三、ds的监控体系

3.1 节点状态存活监控

3.1.1 背景介绍

由于线上环境会因为各种资源占用出现宕机或接近宕机的状态(机器可以正常进入,但不能进行组件正常执行,例:磁盘写满、网络波动等),但dolphinscheduler本身没有针对组件不可用状态监控或恢复的机制功能。

如果正好在没人使用ds执行手动任务或进行测试时,很难察觉组件的异常状态,如果在周末出现问题时,则会影响大量的任务运行,需要花费较长时间进行修复。

因此,实现节点状态存活监控旨在:1、实现组件的状态监控,并尝试自愈;2、快速上报组件内自愈失败的异常节点,减少对线上任务的执行影响。

3.1.2 功能描述

脚本监控ds的worker-server、master-server存活状态,发现状态异常时先进行重新启动,再次监控状态还是异常时,进行告警,

因为不同节点在组件中的角色不同,因此对告警等级进行了下图的设定:

角色 影响程度 报警等级
master-server 挂掉一个:短时间不影响组件的整体运行 P5
master-server 全部挂掉:影响组件的正常运行 P1
worker-server 挂掉一个:影响在对应节点上所有任务的执行 P1

效果示例:

3.2 工作流定时状态上线监控

3.2.1 背景介绍

由于线上定时任务的调度基本都在ds上执行,每天会有较多的上线操作,会对线上工作流进行下线修改操作,如果上线过程遗漏掉定时上线或者工作流上线,就会造成任务漏跑,严重的会影响其他的正常定时调度的工作流。

因此,工作流定时状态上线监控旨在:每天夜里在凌晨任务高峰段开始前确认线上正式工作流的上线状态、定时状态。

3.2.2 功能描述

从ds元数据库查询,所有有上线定时设置的工作流,再逐一进行递归验证工作流的上线状态和定时上线状态,以及子工作流的上线状态,未上线时进行上报。

3.2.3 实现逻辑

1、从元数据库获取由定时设置且工作流名称未包含'修复'、'测试'等关键词的工作流信息;

2、遍历上述获取工作流,对其定时状态进行判断,如果未上线:则进行告警通知;

3、如果2中工作流定时上线,则遍历工作流内节点信息,获取所有的子节点类型节点,对子节点指向的工作流进行工作流上线状态的判断;正常则进行本步骤继续递归子工作流节点直至工作流内没有子工作流类型的节点为止;否则,就进行告警通知。

实现效果:

3.3 ds长时间执行工作流监控

3.3.1 背景介绍

目前,线上大多数的工作流执行不会超过4个小时,但存在:1、特殊工作流长时间执行;2、异常工作流执行:长时间请求等待、依赖卡住等情况。

开发ds长时间执行工作流监控,旨在:提醒当前线上存在超长时间执行工作流,方便异常情况的停止并及时修复;也方便特殊工作流的分析优化。

3.3.2 功能描述

上报当前执行时长超过4小时(基本是执行异常事件)的工作流名称。

3.3.3 代码实现

1、获取超长时间执行的工作流信息,实现sql如下:

vbnet 复制代码
select pjname, pname, stat from
(select process_definition_code, TIMESTAMPDIFF(minute , start_time,now()) stat from t_ds_process_instance where state = 1) instance
join
(select project.name pjname, process.name pname, process.code
from t_ds_project project join t_ds_process_definition process on process.project_code = project.code
whereprocess.name not like '%测试%'
and process.name not like '%修复%') def
on def.code = instance.process_definition_code
where stat > 240

2、判断是不是指定端特殊工作流(为这类工作流设置单独的告警时长);

3、超出设置阈值,则进行上报。

实现效果:

3.4 ds shell节点未添加重试监控

3.4.1 背景介绍

由于ds上的执行任务受集群机器的状态影响、关联组件(比如:zookeeper、MySQL等)的影响、网络影响,不能保证任务节点在定时调度时,一次就一定能执行成功,所以需要进行重试次数的设置。

本监控实现对当日新增的节点未添加重试进行上报提醒。

3.4.2 功能描述

上报当前shell类型节点未增加重试的工作流信息。

3.4.3 代码实现

从元数据库获取当日新增的、类型为'SHELL'的、未被禁止的、所属工作流已上线的、失败重试次数为0的节点信息,sql实现如下:

csharp 复制代码
select project.name pjname, process.name pname, task.name tname
from t_ds_task_definition task
join t_ds_project project on task.project_code = project.code
left join t_ds_process_definition process on locate(task.code, process.locations) > 0
where process.release_state = 1 and task.task_type in ('SHELL', 'SQL')
and task.fail_retry_times = 0 and process.release_state = 1 and task.flag = 1
and (task.update_time >= '{}' or task.create_time >= '{}')

对获取的信息进行汇总上报。

3.5 ds依赖节点未设置超时失败监控

3.5.1 背景介绍

由于ds对依赖信息的判断在没有对应实例的情况下,会进行等待然后判断,一直循环。那么不设置超时失败就会导致工作流在依赖执行异常的情况下(例如:未执行、或长时间执行不出来),就会一直进行判断,这同样可能造成大量工作流不能执行要花费较多时间进行修复,且要在修复前手动进行停止。本监控旨在解决依赖节点超时时长相关的监控,旨在保证依赖时长始终控制在合理且有效的范围内

3.5.2功能描述

上报未设置超时失败的依赖类型节点、设置的不是超时失败的依赖节点、以及依赖节点执行时长接近设置时长的节点

3.5.3 代码实现

1、先获取每日执行的、依赖节点类型的任务实例,关联任务节点定义表,如果未设置超时、设置的不是超时失败,则进行上报;

2、获取近七天内执行的、依赖节点类型的任务且依赖执行时长超过1分钟的实例信息,统计依赖执行总时长/依赖执行次数的平均执行时长,平均执行时长接近设置时长的80%,则进行上报;

3、获取当日执行的、依赖执行时长超过设置时长90%,进行上报。

实现效果:

四、效率工具

4.1 工作流的依赖情况查询

4.1.1 背景介绍

因为ds中工作流之间会有较多的依赖关系,因此在对工作流的拓扑进行调整、定时进行修改时,要先确认对他有依赖的下游工作流有哪些,需要逐一确认,调整对其是否有影响,是否需要随之改动。

4.1.1 功能描述

查询当前环境所有依赖你指定的工作流的工作流信息。

4.1.2 代码实现

1、根据输入的项目名称、工作流名称获取对应的id;

2、在任务定义表中获取依赖类型节点的信息中包含1中查询到的id信息的任务节点id;

3、将2中获取的id关联工作流定义表、项目表,获取其所在的项目和工作流。实现效果:

指定项目和工作流名称:

查询结果:

4.2 工作流信息快捷查询

4.2.1 功能描述

在ds元数据库中工作流(process)和节点(task)都是通过project_code和项目进行关联的,因此,查询对应节点和工作流信息时,要经过较多处理,故进行一个基础sql实现项目、工作流和节点的信息关联,这样在实际应用中只需要进行简单其他筛选条件的添加。

4.2.2 代码实现

csharp 复制代码
select project.name pjname, process.name pname, task.name tname
from t_ds_task_definition task
join t_ds_project project on task.project_code = project.code
left join t_ds_process_definition process on locate(task.code, process.locations) > 0

这样在实际应用中,只需要增加where条件和需要的字段就可以获取所有需要的信息

举例:获取所有'SQL'类型节点的信息:

csharp 复制代码
select project.name pjname, process.name pname, task.name tname
from t_ds_task_definition task
join t_ds_project project on task.project_code = project.code
left join t_ds_process_definition process on locate(task.code, process.locations) > 0
where task.task_type = 'SQL'

五、展望

在本文介绍的大数据团队对dolphinscheduler的优化实践、监控体系和效率工具基础上,为保证任务的稳定运行同时优化项目的调度、保障资源分配合理且充足,我们将会继续通过智能编排算法进行以下方面优化:

1、结合历史调度实例、集群资源空闲状态、追溯依赖关系输出合适的修改建议;

2、元数据导入dataHub,方便溯源工作流之间的真实的依赖关系,在脚本中自动进行递归改动,对改动信息进行输出。

相关推荐
£菜鸟也有梦3 分钟前
从0到1,带你走进Flink的世界
大数据·hadoop·flink·spark
Data-Miner43 分钟前
可编辑PPT | 基于大数据中台新能源智能汽车应用解决方案汽车大数据分析与应用解决方案
大数据·汽车
风象南1 小时前
SpringBoot实现简易直播
java·spring boot·后端
这里有鱼汤1 小时前
有人说10日低点买入法,赢率高达95%?我不信,于是亲自回测了下…
后端·python
武子康2 小时前
Java-39 深入浅出 Spring - AOP切面增强 核心概念 通知类型 XML+注解方式 附代码
xml·java·大数据·开发语言·后端·spring
米粉03052 小时前
SpringBoot核心注解详解及3.0与2.0版本深度对比
java·spring boot·后端
G皮T2 小时前
【Elasticsearch】Elasticsearch 核心技术(二):映射
大数据·elasticsearch·映射·搜索·动态映射·mappings
一只帆記3 小时前
SpringBoot EhCache 缓存
spring boot·后端·缓存
yuren_xia6 小时前
Spring Boot中保存前端上传的图片
前端·spring boot·后端
隰有游龙8 小时前
hadoop集群启动没有datanode解决
大数据·hadoop·分布式