关于新一代大数据任务调度 - Apache DolphinScheduler

 

Apache DolphinScheduler(incubator) 于 17 年在易观数科立项, 19 年 8 月进入 Apache 孵化器,已有 400+ 公司在生产上使用,代码+文档贡献者 200+ 位,贡献者主要来自 80+ 家公司及机构。DolphinScheduler (简称DS)致力于使大数据任务调度开箱即用,它以拖拉拽的可视化方式将各种任务间的关系组装成 DAG(有向无环图),并实时监控整个数据pipeline的运行状态,同时支持失败重试、重跑、恢复失败、补数等大数据常用操作

01

问题描述

我们在 DS 1.3.4 新版本基础上添加了 “获取 sql 类型任务运行结果数据保存到文件” 的功能,在运行工作流的时候,调度一直在运行中,api-server 日志正常,master-server 没有报错,worker-server 也没有报错,流程实例在运行中,任务实例处于已提交状态,然后不动了,卡死在这里了。

02

问题定位

流程实例在执行中,说明 master-server 改变了流程实例状态,排查到 master-server 日志中没有 Netty 发送部分,于是对源码进行了分析:

将任务添加到备用队列
private void addTaskToStandByList(TaskInstance taskInstance){
logger.info("add task to stand by list: {}", taskInstance.getName());
try {
readyToSubmitTaskQueue.put(taskInstance);
} catch (Exception e) {
logger.error("add task instance to readyToSubmitTaskQueue error");
}
}

TaskPriorityQueueConsumer队列优先级消费线程会从备用任务队列中不断的取出优先级高的队列,进行分发

public void run() {
List<String> failedDispatchTasks = new ArrayList<>();
while (Stopper.isRunning()){
try {
int fetchTaskNum = masterConfig.getMasterDispatchTaskNumber();
failedDispatchTasks.clear();
for(int i = 0; i < fetchTaskNum; i++){
if(taskPriorityQueue.size() <= 0){
Thread.sleep(Constants.SLEEP_TIME_MILLIS);
continue;
}
// 从任务备用队列中取出优先级高的任务
String taskPriorityInfo = taskPriorityQueue.take();
TaskPriority taskPriority = TaskPriority.of(taskPriorityInfo);
//分发备用任务队列中的任务
boolean dispatchResult = dispatch(taskPriority.getTaskId());
if(!dispatchResult){
failedDispatchTasks.add(taskPriorityInfo);
}
}
if (!failedDispatchTasks.isEmpty()) {
for (String dispatchFailedTask : failedDispatchTasks) {
taskPriorityQueue.put(dispatchFailedTask);
}
// If there are tasks in a cycle that cannot find the worker group,
// sleep for 1 second
if (taskPriorityQueue.size() <= failedDispatchTasks.size()) {
TimeUnit.MILLISECONDS.sleep(Constants.SLEEP_TIME_MILLIS);
}
}
}catch (Exception e){
logger.error("dispatcher task error",e);
}
}
}

接着进行分析,TaskPriorityQueueConsumer#dispatch 任务分发方法新增日志。

protected boolean dispatch(int taskInstanceId) {
logger.info("dispatch taskInstanceId:{}", taskInstanceId);
boolean result = false;
try {
TaskExecutionContext context = getTaskExecutionContext(taskInstanceId);
ExecutionContext executionContext = new ExecutionContext(context.toCommand(), ExecutorType.WORKER, context.getWorkerGroup());
//新增打印分发内容日志,否则不知道TaskPriorityQueueConsumer是否在分发任务
logger.info("dispatch executionContext:{}", JSONObject.toJSONString(executionContext));
if (taskInstanceIsFinalState(taskInstanceId)){
// when task finish, ignore this task, there is no need to dispatch anymore
return true;
}else{
result = dispatcher.dispatch(executionContext);
}
} catch (ExecuteException e) {
logger.error("dispatch error",e);
}
return result;
}

ExecutorDispatcher#dispatch实际的任务分发执行器,将会根据最小权重算法获取到work执行机器的host,然后去执行最终的netty发送操作,这里新增了打印host机器日志操作,我们就知道到时候接收任务的机器是哪一台,可以到那一台上去看日志。

public Boolean dispatch(final ExecutionContext context) throws ExecuteException {
/**
* get executor manager
*/
ExecutorManager<Boolean> executorManager = this.executorManagers.get(context.getExecutorType());
if(executorManager == null){
throw new ExecuteException("no ExecutorManager for type : " + context.getExecutorType());
} /**
* host select
*/ Host host = hostManager.select(context);
logger.info("host info:{}", JSONObject.toJSONString(host));
if (StringUtils.isEmpty(host.getAddress())) {
throw new ExecuteException(String.format("fail to execute : %s due to no suitable worker , " +
"current task need to %s worker group execute",
context.getCommand(),context.getWorkerGroup()));
}
context.setHost(host);
executorManager.beforeExecute(context);
try {
/**
* task execute
*/
return executorManager.execute(context);
} finally {
executorManager.afterExecute(context);
}
}

NettyExecutorManager#doExecute Netty最终发送命令到 worker-server 的方法,打印一下发送的 command 和 host

private void doExecute(final Host host, final Command command) throws ExecuteException {
/**
* retry count,default retry 3
*/
int retryCount = 3;
boolean success = false;
do {
try {
logger.info("send command:{} host:{}", command, host);
nettyRemotingClient.send(host, command);
success = true;
} catch (Exception ex) {
logger.error(String.format("send command : %s to %s error", command, host), ex);
retryCount--;
try {
Thread.sleep(100);
} catch (InterruptedException ignore) {}
}
} while (retryCount >= 0 && !success); if (!success) {
throw new ExecuteException(String.format("send command : %s to %s error", command, host));
}
}

03

尝试解决

首先判断是否 master- server 这出了异常

我们在 master-server 模块里添加了一些日志,修改完 master-server 后,重新启动一下,运行一个流程,查看日志,发现最终打印了 NettyExecutorManager#doExecute 方法的发送日志里面包括了command 命令和 host,说明 master-server 无异常

根据 host 查看接受命令的 worker-server

发现 work-server 依然没有任何日志打印。

观察 logback-worker.xml 发现 worker 只打印线程名称开头为 “Worker-” 的日志

public class WorkerLogFilter extends Filter<ILoggingEvent> {
/**
* level
*/
Level level; /**
* Accept or reject based on thread name
* @param event event
* @return FilterReply
*/
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getThreadName().startsWith("Worker-")){
return FilterReply.ACCEPT;
} return FilterReply.DENY;
}
public void setLevel(String level) {
this.level = Level.toLevel(level);
}
}

下面开启新一波的修改:

1、手动修改if 判断为event.getThreadName().startsWith("Worker-") || event.getThreadName().startsWith("Netty"),然后将 netty 线程名命名为以 “Netty” 开头。

private final ExecutorService defaultExecutor = Executors.newFixedThreadPool(Constants.CPUS, new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("NettyRemotingServer")
            .build());

2、work-server 中 netty 接收命令的地方新增日志打印,修改部分 warn 级别日志为info,打印到日志文件方便分析。

private void processReceived(final Channel channel, final Command msg) {
logger.info("processReceived command:{} channel:{}", JSONObject.toJSONString(msg), channel);
final CommandType commandType = msg.getType();
final Pair<NettyRequestProcessor, ExecutorService> pair = processors.get(commandType);
if (pair != null) {
Runnable r = new Runnable() { @Override
public void run() {
try {
pair.getLeft().process(channel, msg);
} catch (Throwable ex) {
logger.error("process msg {} error", msg, ex);
}
}
};
try {
pair.getRight().submit(r);
} catch (RejectedExecutionException e) {
//修改warn为info
logger.info("thread pool is full, discard msg {} from {}", msg, ChannelUtils.getRemoteAddress(channel));
}
} else {
//修改warn为info
logger.info("commandType {} not support", commandType);
}
}

根据 host 查看接收命令的 worker-server

重启后运行流程,发现 work-server 日志打印正常了,接收到任务了,在获取 sql 类型任务数据路径失败报错了。

04

最终解决

获取 sql 类型任务数据这部分是我们在 DS 上新增的功能,定位到问题后,我们修复了获取 sql 类型任务数据路径失败问题,至此流程实例运行中卡死问题解决!

05

参与开源贡献

贡献不限于代码,答疑,完善文档,编写相关文章也有可能成为 Committer 。文章内容包含不限于:DS 部署,使用,经验分享,故障处理,源码分析等等。

DolphinScheduler 社区参与贡献的方式,包括:

投稿欢迎联系微信:

社区汇总了以下适合新手的问题列表:https://github.com/apache/incubator-dolphinscheduler/issues/4124

如何参与贡献链接:https://dolphinscheduler.apache.org/zh-cn/docs/development/contribute.html

来吧,DolphinScheduler开源社区需要您的参与,为中国开源崛起添砖加瓦吧,哪怕只是小小的一块瓦,汇聚起来的力量也是巨大的

如果您想参与贡献,我们也有个开发者种子孵化群,可以添加微信(easyworkflow) ,添加时请说明想参与贡献哈

关于滴普科技

 

北京滴普科技成立于2018年,是全场景数据智能服务商。公司致力于以云原生微服务框架的研发,综合5G、IoT、大数据、AI、云计算等新技术,形成可高度扩展的商业智能和产业智能的平台产品,为组织提供全场景数据智能服务。


喜欢 DolphinScheduler 的话,别忘了「分享」「收藏」点赞」「在看」,让更多人知道我们哦????

点击“阅读原文”,直达 Apache DolphinScheduler 官网

让 DolphinScheduler 1.3.4 开启 Netty 日志打印,解决流程实例一直在运行中的问题的更多相关文章

  1. LOG4NET开源日志dll引用流程,在net3.5中已经实践ok

    一,在app.config中配置 <?xml version="1.0"?><configuration> <configSections> & ...

  2. ubuntu14.04开启crontab日志

    ubuntu默认没有开启cron日志记录 1. 修改rsyslog sudo vim /etc/rsyslog.d/50-default.conf cron.* /var/log/cron.log # ...

  3. Nginx 开启 debug 日志的办法

    译序:一般来讲,Nginx 的错误日志级别是 error,作为 Nginx 用户来讲,你设置成 info 就足够用了.         但有时有些难以挖掘的 bug,需要看到更详细的 debug 级别 ...

  4. 开启bin-log日志mysql报错:This function has none of DETERMINISTIC, NO SQL解决办法

    开启bin-log日志mysql报错:This function has none of DETERMINISTIC, NO SQL解决办法: 创建存储过程时 出错信息: ERROR 1418 (HY ...

  5. mysql开启binlog日志和慢查询日志

    1)首先,为什么要开启binlog日志和慢查询日志呢? binlog日志会记录下数据库的所以增删改操作,当不小心删除.清空数据,或数据库系统出错,这时候就可以使用binlog日志来还原数据库,简单来说 ...

  6. ubuntu开启慢日志

    ubuntu 开启mysql日志记录 1.找到mysql的配置文件sudo vim /etc/mysql/my.cnf将下面两行的#去掉#general_log_file = /var/log/mys ...

  7. mysql开启查询日志功能

    1.开启查询日志  https://www.cnblogs.com/kerrycode/p/7130403.html MYsql 查询日志配置    mysql> show variables ...

  8. 转载Linux下开启MySQL日志

    转载https://blog.csdn.net/weixin_38187469/article/details/79273962 开启mysql日志   1.查看日志是否启用 mysql> sh ...

  9. 【MySQL解惑笔记】Mysql5.7.x无法开启二进制日志

    一.开启二进制日志 1)未开启二进制日志之前: mysql> show variables like 'log_bin'; +---------------+-------+ | Variabl ...

随机推荐

  1. node包的降版本

    1.安装版本更高的node包直接到官网去安装. 2.从版本高的node包,降低到版本低的node包. 要先卸载现在的node包,在菜单栏中可以删除. 然后通过https://nodejs.org/zh ...

  2. Border性质习题与证明

    KMP 第一次接触 \(border\) 都是先从 KMP 开始的吧. 思想在于先对于一个串自匹配以求出 fail 指针(也就是 border) 然后就可以在匹配其他串的时候非常自然的失配转移.在此顺 ...

  3. 「NOI2019」序列

    NKOJ卡常卡不过QAQ description 给两个A,B序列,让你分别在A,B中各选k个数,其中至少有L对下标相等. Solution 把问题转化为至多选n-K对下标不同的对. 配对问题就用费用 ...

  4. 【原创】项目一GoldenEye

    实战流程 1,通过nmap查找本段IP中存活的机器 ┌──(root㉿whoami)-[/home/whoami/Desktop] └─# nmap -sP 192.168.186.0/24 排查网关 ...

  5. idea 中菜单栏定位到类的图标消失(小齿轮按钮)

    本文链接:https://www.cnblogs.com/hchengmx/p/14533349.html 在2019.2以及以下版本 勾选:Autoscroll from source: 在2019 ...

  6. ExtJS 布局-Border 布局(Border layout)

    更新记录: 2022年6月11日 发布. 2022年6月1日 开始. 1.说明 边框布局允许根据区域(如中心.北部.南部.西部和东部)指定子部件的位置.还可以调整子组件的大小和折叠. 2.设置布局方法 ...

  7. MongoDB学习总览

    第1部分: MongoDB入门(第1~6章) 该部分介绍MongoDB的基本概念及入门知识. 通过该部分的学习,读者可对MongoDB自身的技术全貌形成一定的认识. 第2部分: MongoDB微服务开 ...

  8. 敲了几万行源码后,我给Mybatis画了张“全地图”

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.说说:"产"后感受 有人跟我说,手写Spring难,手写Mybatis ...

  9. 3D可视化在化工领域的应用及案例分享

    2020年,中办.国办印发的<关于全面加强危险化学品安全生产工作的意见>中重点提出应加快"推进化工园区安全生产信息化.智能化平台建设,实现对园区内企业.重点场所.重大危险源.基础 ...

  10. BUUCTF-穿越时空的思念

    穿越时空的思念 音频题的话一般是摩尔斯电码,软件打开音频发现 短的为. 长的为- 空缺的为空格 ..-. ----- ..--- ----. -... -.. -.... ..-. ..... ... ...