Dolphinscheduler Master 节点的主要工作是掌控整个 DAG 工作流的生命周期,决定任务的执行顺序,分发任务至合适的 worker 节点,及时更新任务的状态信息。(文章源码基于dolphinscheduler 3.1.8)
- 用户手动触发任务或任务定时调起时会根据任务相关信息,创建指令插入数据库。该指令中包含任务发起所需要的关键信息,如任务定义 id,执行参数,指令类型等。
commandMapper.insert(command);
- MasterSchedulerBootstrap 是 Master 的一个调度线程,主要工作是从数据库中查询待执行的指令,根据指令信息封装任务执行线程实例,添加到队列中等待执行。该线程在 Master 服务启动时跟随启动。
// 调度线程启动
this.masterSchedulerBootstrap.start();
// 查询待执行的指令
List<Command> commands = findCommands();
// 指令会转换为相应的工作流实例,同时创建任务执行线程实例,缓存到processInstanceExecCacheManager,接着新建一个工作流事件添加到队列WorkflowEventQueue等待出列
WorkflowExecuteRunnable workflowRunnable = new WorkflowExecuteRunnable(processInstance,
processService,
processInstanceDao,
nettyExecutorManager,
processAlertManager,
masterConfig,
stateWheelExecuteThread,
curingGlobalParamsService);
processInstanceExecCacheManager.cache(processInstance.getId(), workflowRunnable);
workflowEventQueue.addEvent(new WorkflowEvent(WorkflowEventType.START_WORKFLOW, processInstance.getId()));
// 该调度线程启动时,同时会启动WorkflowEventLooper
workflowEventLooper.start();
- WorkflowEventLooper 是一个轮询线程,负责从 WorkflowEventQueue 队列中获取事件去执行。
// 获取一个事件
workflowEvent = workflowEventQueue.poolEvent();
// 根据事件类型,获取对应的事件处理器处理事件。例:工作流事件类型:START_WORKFLOW, 对应的处理器WorkflowStartEventHandler
WorkflowEventHandler workflowEventHandler =workflowEventHandlerMap.get(workflowEvent.getWorkflowEventType());
workflowEventHandler.handleWorkflowEvent(workflowEvent);
其中 workflowEventHandlerMap 在程序启动时初始化。
public interface WorkflowEventHandler {
void handleWorkflowEvent(WorkflowEvent workflowEvent) throws WorkflowEventHandleError, WorkflowEventHandleException;
WorkflowEventType getHandleWorkflowEventType();
}
@Component
public class WorkflowStartEventHandler implements WorkflowEventHandler {
}
@Component
public class WorkflowEventLooper {
@Autowired
private List<WorkflowEventHandler> workflowEventHandlerList;
@PostConstruct
public void init() {
workflowEventHandlerList.forEach(
workflowEventHandler -> workflowEventHandlerMap.put(workflowEventHandler.getHandleWorkflowEventType(),
workflowEventHandler));
}
}
- WorkflowStartEventHandler 处理器会从缓存 processInstanceExecCacheManager 中拿到对应的任务执行线程实例 workflowExecuteRunnable,然后提交到异步线程池执行。
CompletableFuture.supplyAsync(workflowExecuteRunnable::call, workflowExecuteThreadPool).thenAccept();
- 接下来就是最核心的线程类 WorkflowExecuteRunnable。 主要用来解析 dag,负责 dag 的整个调度流程,包括任务的初始化、提交、分发、执行控制,任务各类事件的处理等。
// 解析dag
buildFlowDag();
// 初始化任务执行队列
initTaskQueue();
// 提交任务执行
submitTaskExec(TaskInstance taskInstance)
submitTaskExec 会根据 taskInstance 的类型去选择不同的任务处理器,不同的任务处理器针对不同的 action 类型有不同的实现方式。例如 shell、python 这类通用任务会选择默认任务处理器 CommonTaskProcessor,这类任务提交后 Master 无需其他操作,直接派发到 worker 执行,而 dependent 任务选择 DependentTaskProcessor,这类任务仅需要 Master 判断依赖的任务是否执行成功,而无需派发到 worker 节点执行。
// 获取任务处理器
ITaskProcessor taskProcessor = TaskProcessorFactory.getTaskProcessor(taskInstance.getTaskType());
// 依次执行对应的操作:提交、派发、执行等
taskProcessor.action(TaskAction.SUBMIT);
taskProcessor.action(TaskAction.DISPATCH);
taskProcessor.action(TaskAction.RUN);
任务处理器插件 TaskProcessor 实现方式。
// 任务处理器接口,定义action方法,处理对任务的不同操作
public interface ITaskProcessor extends PrioritySPI {
boolean action(TaskAction taskAction);
}
// 基础任务处理器,根据不同的action类型执行不同的动作方法,不同的任务处理器针对不同的动作有各自的实现逻辑
public abstract class BaseTaskProcessor implements ITaskProcessor {
@Override
public boolean action(TaskAction taskAction) {
switch (taskAction) {
case SUBMIT:
result = submit();
break;
case RUN:
result = run();
break;
case DISPATCH:
result = dispatch();
break;
default:
logger.error("unknown task action: {}", taskAction);
}
}
// 定义了公共的抽象行为,子类需要按照各自的逻辑去实现
protected abstract boolean submitTask();
protected abstract boolean runTask();
protected abstract boolean dispatchTask();
}
// 具体的任务处理器子类
// 通用的任务处理器
@AutoService(ITaskProcessor.class)
public class CommonTaskProcessor extends BaseTaskProcessor {
@Override
protected boolean submitTask() {
// 核心逻辑只需要创建任务实例入库
TaskInstance task = submitTaskInstanceToDB(taskInstance, processInstance);
}
@Override
public boolean dispatchTask() {
// 初始化队列,该队列是一个带有优先级的阻塞队列,由PriorityBlockingQueue实现,确保并发安全,并可以自定义任务的优先级
this.taskUpdateQueue = SpringApplicationContext.getBean(TaskPriorityQueueImpl.class);
// 创建带优先级的任务
TaskPriority taskPriority = new TaskPriority()
// 任务push至优先级阻塞队列中
taskUpdateQueue.put(taskPriority);
}
@Override
public boolean runTask() {
// 具体的任务分发至worker节点执行,这里无需其他逻辑
return true;
}
}
// Dependent任务处理器
@AutoService(ITaskProcessor.class)
public class DependentTaskProcessor extends BaseTaskProcessor {
@Override
public boolean submitTask() {
TaskInstance task = submitTaskInstanceToDB(taskInstance, processInstance);
// 除了创建任务实例插入数据库外,还需要初始化dependetn任务的参数
initDependParameters();
}
@Override
public boolean runTask() {
// 具体的执行逻辑,需要判断所有的任务依赖是否都执行成功
if (!allDependentItemFinished) {
allDependentItemFinished = allDependentTaskFinish();
}
if (allDependentItemFinished) {
getTaskDependResult();
endTask();
}
return true;
}
@Override
protected boolean dispatchTask() {
// 无需下发到worker节点去执行任务
return true;
}
}
这里仅列举了典型的两种任务处理器,除此之外,还有 SwitchTaskProcessor, ConditionTaskProcessor 等。
- 需要 worker 节点执行的任务,会派发到 TaskPriorityQueue 类型的队列中,TaskPriorityQueueConsumer 是一个消费线程,他会从队列中取出任务,根据一定的负载均衡策略分发到指定的 worker 节点执行。
public class TaskPriorityQueueConsumer extends BaseDaemonThread {
// 存储任务实例的队列
@Autowired
private TaskPriorityQueue<TaskPriority> taskPriorityQueue;
@Override
public void run() {
// 取出任务
TaskPriority taskPriority = taskPriorityQueue.poll(Constants.SLEEP_TIME_MILLIS, TimeUnit.MILLISECONDS);
// 根据负载均衡策略选择具体执行任务的worker节点
Host host = hostManager.select(context);
// 利用rpc服务发送任务至worker
nettyRemotingClient.send(host, command);
}
}