关于新一代大数据任务调度 - 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. Javabean使用实例

    1.login.jsp <%@ page language="java" contentType="text/html; charset=utf-8" p ...

  2. axios的请求参数格式(get、post、put、delete)

    1.get请求方式: axios.get(url[, config]) // [字符拼接型]axios.get(url?id=123&status=0') // 等同于 axios.get(u ...

  3. 一次生产环境的docker MySQL故障

    问题 昨天下午本来要去吃下午茶,然后前端小伙伴突然说接口怎么崩了,我登上sentry一看,报错了 (2005, "Unknown MySQL server host 'mysql' (-3) ...

  4. IDEA找不到类但实际存在的问题解决

    不知道某天开始Idea就开始抽风了. 现象: 一个service的接口类,就在同一个包下,但总是找不到,编辑器一直标红 编译可以通过 说明类本身应该是没什么问题的.问题是怎么重新编译重新reload ...

  5. AR Engine运动跟踪能力,高精度实现沉浸式AR体验

    随着电子产品的普遍应用,AR技术也开始广泛普及,在游戏.电商.家装等领域都有涉及.比如,在室内设计时,我们可以通过AR技术在实际场景中进行虚拟软装的搭配,运用华为AR Engine运动跟踪能力在实际应 ...

  6. Linux下删除Mysql

    1.检查mysql服务并关闭相应的进程 [root@bp18425116f0cojd1vnz ~]# ps -ef |grep mysql root 1492 1 0 10:23 ? 00:00:00 ...

  7. camunda流程引擎概念术语

    前言 本文重点介绍开源流程引擎camunda的核心概念,这些概念同样适用于JBMP.Activiti.Flowable流程引擎,了解这些基本概念和原理,使用流程引擎API将更得心应手. 一.Proce ...

  8. 16.Nginx优化与防盗链

    Nginx优化与防盗链 目录 Nginx优化与防盗链 隐藏版本号 修改用户与组 缓存时间 日志切割 小知识 连接超时 更改进程数 配置网页压缩 配置防盗链 配置防盗链 隐藏版本号 可以使用 Fiddl ...

  9. 1.4 操作系统的其余功能 -《zobolの操作系统学习札记》

    1.4 操作系统的其余功能 操作系统除了虚拟化.并发.存储管理三个主要功能,还有许多子功能,我主要介绍几种常见的功能比如 目录 1.4 操作系统的其余功能 稳定性 高性能 隔离保护 易用性(可视化) ...

  10. HDLBits->Verilog Language->Modules:Hierarchy->Modules and vectors

    题目要求如上不再赘述,主要关注到最后的四选一多路选择器. 最初编写的选择器代码如下 always@(sel) case(sel) 2'd0:q <= d; 2'd1:q <= in1; 2 ...