目录
Apache DolphinScheduler 是一个分布式、易扩展且可视化的调度器,用于企业级大数据工作流的创建、调度及监控。Master 节点作为核心调度组件,负责接收任务指令、调度 DAG 工作流、分发任务至 Worker,并处理集群的容错和高可用机制。本篇文章聚焦于 DolphinScheduler 3.2.0 中 Master 启动的源码流程,通过梳理其启动顺序与核心模块,帮助读者快速理解 Master 服务的初始化与调度逻辑。
本文结构如下:
-
手动调度工作流触发原理
-
MasterServer 启动入口与整体流程
-
Master RPC 服务启动(Server & Client)
-
插件加载机制
-
注册中心客户端初始化与心跳维护
-
核心调度引擎启动
-
事件处理服务
-
故障转移线程
-
Quartz 调度器服务启动
-
Master 和 Worker 容错(Failover)流程
-
总结与思考
1. 手动调度工作流触发原理
在 Web UI 界面通过“手动执行”按钮触发工作流时,其背后并不会立即开始执行调度,而是将一个 Command 写入数据库。执行入口在 ExecutorController
:
@PostMapping(value = "start-process-instance") public Result startProcessInstance(@RequestBody StartProcessInstanceCommand command) { // 参数校验与权限判断... executorService.execProcessInstance(command); return Result.success(); }
进一步调用链:
ExecutorServiceImpl#execProcessInstance(...) → createCommand(...) → CommandServiceImpl#createCommand(Command) → CommandMapper#insert(Command)
此处仅在 t_ds_command
表中插入一条记录。Master 节点在启动后,会不断轮询并消费该表中的命令,随后真正恢复并执行工作流实例。
2. MasterServer 启动入口与整体流程
Master 的启动入口位于类 org.apache.dolphinscheduler.server.master.MasterServer
,其 @PostConstruct
标注的 run()
方法按照顺序完成各模块的初始化与启动:
@PostConstruct public void run() throws SchedulerException { // 1. 启动 RPC 服务(Server + Client) masterRPCServer.start(); masterRpcClient.start(); // 2. 加载 Task 插件 taskPluginManager.loadPlugin(); // 3. 启动注册中心客户端(注册 Master & 监听集群节点变更) masterRegistryClient.start(); masterRegistryClient.setRegistryStoppable(this); // 4. 启动核心调度引擎 masterSchedulerBootstrap.start(); // 5. 启动异步事件处理服务 eventExecuteService.start(); // 6. 启动故障转移线程 failoverExecuteThread.start(); // 7. 启动 Quartz 调度服务 schedulerApi.start(); // 添加 JVM 关闭钩子 Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (!ServerLifeCycleManager.isStopped()) { close("MasterServer shutdownHook"); } })); }
下面将逐一解析各步骤的源码细节与设计思路。
3. Master RPC 服务启动
Master 节点内部通过 Netty 实现 RPC 通信,分为 RPC Server(监听来自 Worker 的心跳与状态上报)与 RPC Client(向 Worker 分发任务指令)。
3.1 启动 RPC Server
MasterRPCServer.start()
方法内部:
public void start() { log.info("Starting Master RPC Server..."); NettyServerConfig serverConfig = masterConfig.getMasterRpcServerConfig(); serverConfig.setListenPort(masterConfig.getListenPort()); this.nettyRemotingServer = new NettyRemotingServer(serverConfig); // 注册所有 MasterRpcProcessor,实现具体的消息处理逻辑 for (MasterRpcProcessor processor : masterRpcProcessors) { this.nettyRemotingServer.registerProcessor(processor); } this.nettyRemotingServer.start(); log.info("Started Master RPC Server..."); }
NettyRemotingServer
构造时根据平台环境选择 Epoll
或 Nio
IO 模型,并初始化 bossGroup 与 workerGroup:
public NettyRemotingServer(NettyServerConfig config) { if (Epoll.isAvailable()) { bossGroup = new EpollEventLoopGroup(1, bossFactory); workGroup = new EpollEventLoopGroup(config.getWorkerThread(), workerFactory); } else { bossGroup = new NioEventLoopGroup(1, bossFactory); workGroup = new NioEventLoopGroup(config.getWorkerThread(), workerFactory); } // 初始化 ServerBootstrap 并注册 Handler }
在 start()
中调用 ServerBootstrap.bind(...)
监听配置端口(默认 5678,用于 RPC):
serverBootstrap .group(bossGroup, workGroup) .channel(NettyUtils.getServerSocketChannelClass()) .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) { initNettyChannel(ch); } }); ChannelFuture future = serverBootstrap.bind(serverConfig.getListenPort()).sync();
3.2 启动 RPC Client
MasterRpcClient.start()
负责初始化 NettyRemotingClient
,但并不主动连接 Worker:
public void start() { client = new NettyRemotingClient(masterConfig.getMasterRpcClientConfig()); log.info("Success initialized MasterRPCClient..."); }
NettyRemotingClient
构造中同样选择 Epoll
或 Nio
环境,初始化 workerGroup
线程池以及 callbackExecutor
、responseFutureExecutor
,并启动心跳扫描:
bootstrap .group(workerGroup) .channel(NettyUtils.getSocketChannelClass()) .handler(new ChannelInitializer<SocketChannel>() { public void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new IdleStateHandler(...)) .addLast(new NettyDecoder(), clientHandler, encoder); } }); responseFutureExecutor.scheduleWithFixedDelay(ResponseFuture::scanFutureTable, 0, 1, TimeUnit.SECONDS);
4. 插件加载机制
DolphinScheduler 通过 Java SPI 机制动态加载 Task 插件,支持 Hive、Spark、Flink 等多种执行引擎。
TaskPluginManager.loadPlugin()
实现:
public void loadPlugin() { PrioritySPIFactory<TaskChannelFactory> factory = new PrioritySPIFactory<>(TaskChannelFactory.class); for (Map.Entry<String, TaskChannelFactory> entry : factory.getSPIMap().entrySet()) { String name = entry.getKey(); TaskChannelFactory plugin = entry.getValue(); taskChannelFactoryMap.put(name, plugin); taskChannelMap.put(name, plugin.create()); } }
PrioritySPIFactory
内部使用 ServiceLoader.load(spiClass)
扫描 META-INF/services
下的实现类,并处理同名冲突:
for (T impl : ServiceLoader.load(spiClass)) { String key = impl.getIdentify().getName(); if (map.containsKey(key)) resolveConflict(impl); else map.put(key, impl); }
5. 注册中心客户端初始化与心跳维护
Master 通过 masterRegistryClient
与 ZooKeeper(或其他注册中心)交互,完成以下工作:
-
注册当前 Master 节点:在
/dolphinscheduler/master
下创建临时节点并写入心跳信息 -
启动心跳线程:定期更新节点数据,避免节点失联
-
监听集群变化:订阅
/dolphinscheduler/servers
,动态感知 Master/Worker 节点上下线
public void start() { this.masterHeartBeatTask = new MasterHeartBeatTask(masterConfig, registryClient); registry(); // 注册自身并启动心跳 registryClient.addConnectionStateListener(new MasterConnectionStateListener(...)); registryClient.subscribe(RegistryNodeType.ALL_SERVERS.getRegistryPath(), new MasterRegistryDataListener()); }
核心注册逻辑:
void registry() { registryClient.remove(masterPath); registryClient.persistEphemeral(masterPath, JSONUtils.toJsonString(heartbeat)); while (!registryClient.checkNodeExists(host, MASTER)) { ThreadUtils.sleep(3000); } masterHeartBeatTask.start(); }
其中 MasterRegistryDataListener
回调了 handleMasterEvent()
与 handleWorkerEvent()
,触发容错或资源清理。
6. 核心调度引擎启动
核心调度引擎由 MasterSchedulerBootstrap
负责启动,其中包含三个部分:
-
恢复命令:查询所有未执行的 Command,将对应的
WorkflowExecuteRunnable
加入执行缓存与事件队列 -
事件循环:
WorkflowEventLooper
不断拉取workflowEventQueue
,根据事件类型调用不同的 Handler -
任务执行器:
MasterTaskExecutorBootstrap
启动线程池与队列,消费待派发任务并调用 RPC 分发给 Worker
public synchronized void start() { super.start(); // part1: 恢复并提交 Command workflowEventLooper.start(); // part2: 事件循环 masterTaskExecutorBootstrap.start(); // part3: 派发任务 }
6.1 恢复 Command
恢复逻辑示例:
List<Command> commands = findCommands(); commands.parallelStream().forEach(cmd -> { Optional<WorkflowExecuteRunnable> opt = factory.create(cmd); if (opt.isPresent()) { cacheManager.cache(id, runnable); workflowEventQueue.addEvent(new WorkflowEvent(START_WORKFLOW, id)); } });
6.2 事件循环
WorkflowEventLooper
实现了 Runnable
:
public void run() { while (RUNNING) { WorkflowEvent event = queue.poolEvent(); try (MDCAutoClosableContext ctx = setWorkflowIdMDC(...)) { handlerMap.get(event.getType()).handle(event); } } }
START_WORKFLOW
类型进入 WorkflowStartHandler
,调用 WorkflowExecuteRunnable.call()
:
public WorkflowStartStatus startWorkflow() { initTaskQueue(); // 初始化 DAG 队列 submitPostNode(null); // 提交第一个节点 return SUCCESS; }
6.3 任务派发
MasterTaskExecutorBootstrap
负责三个队列的启动:
globalTaskDispatchLooper.start(); masterDelayTaskLooper.start(); asyncMasterTaskDelayLooper.start();
其中 globalTaskDispatchLooper
从 globalTaskDispatchWaitingQueue
获取 DefaultTaskExecuteRunnable
,调用 taskDispatcher.dispatch()
:
Message msg = taskDispatchRequest.convert2Command(); masterRpcClient.sendSyncCommand(host, msg, timeout);
7. 事件处理服务
事件处理分为两类:
-
Workflow 事件:如任务状态变更、流程阻塞等
-
Stream 事件:针对流式任务的自定义事件
EventExecuteService.start()
启动线程池,不断从 stateEvents
与 taskEvents
队列消费:
workflowEventHandler(); // 提交到 workflowExecuteThreadPool streamTaskEventHandler(); // 提交到 streamTaskExecuteThreadPool
8. 故障转移线程
failoverExecuteThread
周期检查 Master 与 Worker 节点的存活情况。对于失联节点,分 Master Failover 与 Worker Failover 两种场景:
8.1 Master Failover
当 Master 宕机,注册中心删除其节点,其他 Master 节点触发 failover:
failoverService.failoverServerWhenDown(serverHost, MASTER);
doFailoverMaster
会:
-
查询需要 Failover 的
ProcessInstance
-
对每个实例调用
processService.processNeedFailoverProcessInstances(processInstance)
,重新将任务写入t_ds_command
8.2 Worker Failover
当 Worker 宕机:
failoverService.failoverServerWhenDown(workerHost, WORKER);
failoverWorker
会查询该 Worker 上执行中的 TaskInstance
,并对每个未完成任务:
-
设置
state = NEED_FAULT_TOLERANCE
-
调用
taskInstanceDao.upsert
更新数据库 -
提交
TaskStateEvent
到workflowExecuteThreadPool
,重新派发给其他 Worker
9. Quartz 调度器服务启动
除了手动触发,Master 还使用 Quartz 实现定时调度功能。SchedulerApi
注入 org.quartz.Scheduler
对象:
@Override public void start() throws SchedulerException { scheduler.start(); }
Quartz Scheduler 被用于周期性触发定时任务,例如流程依赖、定时工作流调度等。
10. 总结与思考
本文详细梳理了 DolphinScheduler 3.2.0 中 Master 节点启动的全流程:从手动写入 Command,到 MasterServer 初始化、RPC 服务搭建、插件加载、注册中心接入,到核心调度引擎与事件处理,再到高可用的故障转移、Quartz 定时调度。Master 的设计展现了模块化与解耦:
-
网络通信:基于 Netty 实现 RPC
-
插件扩展:Java SPI 动态加载 TaskChannel
-
高可用:ZooKeeper 心跳与 Failover 服务
-
调度引擎:结合并行流与事件驱动架构
-
定时任务:Quartz 调度补充
在源码层面,核心逻辑均以 CRUD 与消息队列为基础,开发者可自行跟进链路中遗漏的细节,并结合社区文档和源码仓库进一步学习。