DolphinScheduler 的 Master 是怎么跑起来的?带你看懂启动源码
Apache DolphinScheduler 是一个分布式、易扩展且可视化的调度器,用于企业级大数据工作流的创建、调度及监控。Master 节点作为核心调度组件,负责接收任务指令、调度 DAG 工作流、分发任务至 Worker,并处理集群的容错和高可用机制。本篇文章聚焦于 DolphinScheduler 3.2.0 中 Master 启动的源码流程,通过梳理其启动顺序与核心模块,帮助读者快速理解 Master 服务的初始化与调度逻辑。
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 与消息队列为基础,开发者可自行跟进链路中遗漏的细节,并结合社区文档和源码仓库进一步学习。
原文链接:https://blog.csdn.net/wen811651208/article/details/149231505
浙公网安备 33010602011771号