2021-11-15---2021SC@SDUSC---DolphinScheduler(8)

2021-11-15
2021SC@SDUSC

2021-11-15-DolphinScheduler(8)

1.run

run之中大多是线程初始化、提交的逻辑。

// start QuartzExecutors
// what system should do if exception
try {
    ProcessScheduleJob.init(processDao);
    QuartzExecutors.getInstance().start();
} catch (Exception e) {
    try {
        QuartzExecutors.getInstance().shutdown();
    } catch (SchedulerException e1) {
        logger.error("QuartzExecutors shutdown failed : " + e1.getMessage(), e1);
    }
    logger.error("start Quartz failed : " + e.getMessage(), e);
}


/**
 *  register hooks, which are called before the process exits
 */
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        if (zkMasterClient.getActiveMasterNum() <= 1) {
            for (int i = 0; i < Constants.DOLPHINSCHEDULER_WARN_TIMES_FAILOVER; i++) {
                zkMasterClient.getAlertDao().sendServerStopedAlert(
                        1, OSUtils.getHost(), "Master-Server");
            }
        }
        stop("shutdownhook");
    }
}));

ProcessScheduleJob.init就是给ProcessScheduleJob一个static字段赋值,也就是给所有的ProcessScheduleJob一个全局的processDao

public static void init(ProcessDao processDao) {
    ProcessScheduleJob.processDao = processDao;
}

源码中processDao的处理有些是传参,有些是getBean,有些又是全局变量。

addShutdownHook的逻辑就比较清晰了,就是添加了进程退出的hook。
先发送预警信息,然后调用stop进行退出了。

FetchTaskThread

worker涉及的线程主要有两个FetchTaskThread、TaskScheduleThread。

FetchTaskThread从名称上来看,是从zk队列拉取任务信息的。
它也是Runnable的一个实现类,就从run入手。

@Override
public void run() {
    while (Stopper.isRunning()){
        InterProcessMutex mutex = null;
        try {
            ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) workerExecService;
            //check memory and cpu usage and threads
            boolean runCheckFlag = OSUtils.checkResource(this.conf, false) && checkThreadCount(poolExecutor);

            Thread.sleep(Constants.SLEEP_TIME_MILLIS);

            if(!runCheckFlag) {
                continue;
            }

            //whether have tasks, if no tasks , no need lock  //get all tasks
            List<String> tasksQueueList = taskQueue.getAllTasks(Constants.DOLPHINSCHEDULER_TASKS_QUEUE);
            if (CollectionUtils.isEmpty(tasksQueueList)){
                continue;
            }
            // creating distributed locks, lock path /dolphinscheduler/lock/worker
            mutex = zkWorkerClient.acquireZkLock(zkWorkerClient.getZkClient(),
                    zkWorkerClient.getWorkerLockPath());


            // task instance id str
            List<String> taskQueueStrArr = taskQueue.poll(Constants.DOLPHINSCHEDULER_TASKS_QUEUE, taskNum);

            for(String taskQueueStr : taskQueueStrArr){
                if (StringUtils.isEmpty(taskQueueStr)) {
                    continue;
                }

                if (!checkThreadCount(poolExecutor)) {
                    break;
                }

                // get task instance id
                taskInstId = getTaskInstanceId(taskQueueStr);

                // mainly to wait for the master insert task to succeed
                waitForTaskInstance();

                taskInstance = processDao.getTaskInstanceDetailByTaskId(taskInstId);

                // verify task instance is null
                if (verifyTaskInstanceIsNull(taskInstance)) {
                    logger.warn("remove task queue : {} due to taskInstance is null", taskQueueStr);
                    removeNodeFromTaskQueue(taskQueueStr);
                    continue;
                }

                Tenant tenant = processDao.getTenantForProcess(taskInstance.getProcessInstance().getTenantId(),
                        taskInstance.getProcessDefine().getUserId());

                // verify tenant is null
                if (verifyTenantIsNull(tenant)) {
                    logger.warn("remove task queue : {} due to tenant is null", taskQueueStr);
                    removeNodeFromTaskQueue(taskQueueStr);
                    continue;
                }

                // set queue for process instance, user-specified queue takes precedence over tenant queue
                String userQueue = processDao.queryUserQueueByProcessInstanceId(taskInstance.getProcessInstanceId());
                taskInstance.getProcessInstance().setQueue(StringUtils.isEmpty(userQueue) ? tenant.getQueue() : userQueue);
                taskInstance.getProcessInstance().setTenantCode(tenant.getTenantCode());

                logger.info("worker fetch taskId : {} from queue ", taskInstId);


                if(!checkWorkerGroup(taskInstance, OSUtils.getHost())){
                    continue;
                }

                // local execute path
                String execLocalPath = getExecLocalPath();

                logger.info("task instance  local execute path : {} ", execLocalPath);

                // init task
                taskInstance.init(OSUtils.getHost(),
                        new Date(),
                        execLocalPath);

                // check and create Linux users
                FileUtils.createWorkDirAndUserIfAbsent(execLocalPath,
                        tenant.getTenantCode(), logger);

                logger.info("task : {} ready to submit to task scheduler thread",taskInstId);
                // submit task
                workerExecService.submit(new TaskScheduleThread(taskInstance, processDao));

                // remove node from zk
                removeNodeFromTaskQueue(taskQueueStr);
            }

        }catch (Exception e){
            logger.error("fetch task thread failure" ,e);
        }finally {
            AbstractZKClient.releaseMutex(mutex);
        }
    }

run还是一个while“死循环”,首先检查了当前资源是否超阈值、线程数是否够用。
然后获取runCheckFlag标志,再之后休眠1秒,再判断前面的结果,
如果为false则进入下一个循环。

调用taskQueue.getAllTasks获取当前所有的任务列表,为空则进入下一次循环

1

申请InterProcessMutex锁,这样同时刻只有一个worker节点从队列中poll任务
这意味着,任务会随机的在worker节点执行。
抢占到锁之后,每次poll固定数量的任务。

2

之前TaskQueue中插入的数据

${processInstancePriority}_${processInstanceId}_${taskInstancePriority}_${taskId}_host1,host2...

获取到任务列表之后,就是一个for循环,依次处理任务。下面简单总结一下其逻辑。

    判断taskQueueStr是否为空。感觉有点多此一举。
    判断当前线程数是否够用
    从taskQueueStr中取到任务ID。就是按照_分隔之后的第四个字段。
    等待任务实例信息插入到数据库。循环30次,每次等待1秒。注释说数据库操作会被延迟,不知道哪里会延迟。
    通过任务id,获取任务实例信息。
    通过任务实例,获取租户信息。
    通过任务实例,获取用户队列信息。为啥不在查询任务实例信息的时候,直接获取到呢?或者在getTaskInstanceDetailByTaskId一次性获取到?
    判断任务实例是否可以在当前节点执行,不能则继续下一个任务处理。这为啥不提前判断呢?调了2次db查询才来判断?
    任务实例初始化
    检查目录、用户是否存在。不存在则创建用户、目录。为啥不是提前建好?每次还要检查一遍。
    提交任务,交给TaskScheduleThread线程执行。
    删除taskQueue中对应的任务节点。

FetchTaskThread功能就是抢占zk锁,从TaskQueue获取任务,然后创建TaskScheduleThread线程去执行。

TaskScheduleThread

TaskScheduleThread的功能应该是要执行具体的逻辑。

@Override
public void run() {

    try {
        // update task state is running according to task type
        updateTaskState(taskInstance.getTaskType());

        logger.info("script path : {}", taskInstance.getExecutePath());
        // task node
        TaskNode taskNode = JSONObject.parseObject(taskInstance.getTaskJson(), TaskNode.class);

        // copy hdfs/minio file to local
        copyHdfsToLocal(processDao,
                taskInstance.getExecutePath(),
                createProjectResFiles(taskNode),
                logger);

        // get process instance according to tak instance
        ProcessInstance processInstance = taskInstance.getProcessInstance();

        // set task props
        TaskProps taskProps = new TaskProps(taskNode.getParams(),
                taskInstance.getExecutePath(),
                processInstance.getScheduleTime(),
                taskInstance.getName(),
                taskInstance.getTaskType(),
                taskInstance.getId(),
                CommonUtils.getSystemEnvPath(),
                processInstance.getTenantCode(),
                processInstance.getQueue(),
                taskInstance.getStartTime(),
                getGlobalParamsMap(),
                taskInstance.getDependency(),
                processInstance.getCmdTypeIfComplement());
        // set task timeout
        setTaskTimeout(taskProps, taskNode);

        taskProps.setTaskAppId(String.format("%s_%s_%s",
                taskInstance.getProcessDefine().getId(),
                taskInstance.getProcessInstance().getId(),
                taskInstance.getId()));

        // custom logger
        Logger taskLogger = LoggerFactory.getLogger(LoggerUtils.buildTaskId(LoggerUtils.TASK_LOGGER_INFO_PREFIX,
                taskInstance.getProcessDefine().getId(),
                taskInstance.getProcessInstance().getId(),
                taskInstance.getId()));

        task = TaskManager.newTask(taskInstance.getTaskType(),
                taskProps,
                taskLogger);

        // task init
        task.init();

        // task handle
        task.handle();

        // task result process
        task.after();

    }catch (Exception e){
        logger.error("task scheduler failure", e);
        kill();
        // update task instance state
        processDao.changeTaskState(ExecutionStatus.FAILURE,
                new Date(),
                taskInstance.getId());
    }

    logger.info("task instance id : {},task final status : {}",
            taskInstance.getId(),
            task.getExitStatus());
    // update task instance state
    processDao.changeTaskState(task.getExitStatus(),
            new Date(),
            taskInstance.getId());
}


还是一步步分析run方法。

    更新任务状态为ExecutionStatus.RUNNING_EXEUTION
    从任务实例获取任务节点信息。
    从HDFS复制文件到本地。包括一些用户上传的资源文件,jar包、SQL文件、配置文件等等。
    构造TaskProps对象。
    初始化任务日志对象。
    构造AbstractTask实例
    依次调用AbstractTask的init、handle、after。
    更新任务实例的状态。异常失败或成功等。

TaskManager.newTask

TaskManager.newTask还是比较重要的,它创建了最终的、具体的、可执行的任务实例。

public static AbstractTask newTask(String taskType, TaskProps props, Logger logger)
  throws IllegalArgumentException {
switch (EnumUtils.getEnum(TaskType.class,taskType)) {
    case SHELL:
    return new ShellTask(props, logger);
  case PROCEDURE:
    return new ProcedureTask(props, logger);
  case SQL:
    return new SqlTask(props, logger);
  case MR:
    return new MapReduceTask(props, logger);
  case SPARK:
    return new SparkTask(props, logger);
  case FLINK:
    return new FlinkTask(props, logger);
  case PYTHON:
    return new PythonTask(props, logger);
  case DEPENDENT:
    return new DependentTask(props, logger);
  case HTTP:
    return new HttpTask(props, logger);
  default:
    logger.error("unsupport task type: {}", taskType);
    throw new IllegalArgumentException("not support task type");
}

至此,终于找到了前端配置任务的具体实现类。其实吧,这个异常抛的没有道理。这个taskType肯定是保存在数据库的,保存之前应该做校验了吧,毕竟是enum转过去的。


  1. 应该用hasTask这样的接口判断吗?此处只是判断是否有作业,获取全部的任务列表就不合适了,有点浪费内存。 ↩︎

  2. 可以基于zookeeper实现一个具有优先级的分布式锁,申请锁时会设置当前客户端的权重,权重大的抢到锁的可能性随之增大。 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋斗的仓鼠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值