DolphinScheduler 3.2.0 Master启动核心源码解析

目录

1. 手动调度工作流触发原理

2. MasterServer 启动入口与整体流程

3. Master RPC 服务启动

3.1 启动 RPC Server

3.2 启动 RPC Client

4. 插件加载机制

5. 注册中心客户端初始化与心跳维护

6. 核心调度引擎启动

6.1 恢复 Command

6.2 事件循环

6.3 任务派发

7. 事件处理服务

8. 故障转移线程

8.1 Master Failover

8.2 Worker Failover

9. Quartz 调度器服务启动

10. 总结与思考


Apache DolphinScheduler 是一个分布式、易扩展且可视化的调度器,用于企业级大数据工作流的创建、调度及监控。Master 节点作为核心调度组件,负责接收任务指令、调度 DAG 工作流、分发任务至 Worker,并处理集群的容错和高可用机制。本篇文章聚焦于 DolphinScheduler 3.2.0 中 Master 启动的源码流程,通过梳理其启动顺序与核心模块,帮助读者快速理解 Master 服务的初始化与调度逻辑。

本文结构如下:

  1. 手动调度工作流触发原理

  2. MasterServer 启动入口与整体流程

  3. Master RPC 服务启动(Server & Client)

  4. 插件加载机制

  5. 注册中心客户端初始化与心跳维护

  6. 核心调度引擎启动

  7. 事件处理服务

  8. 故障转移线程

  9. Quartz 调度器服务启动

  10. Master 和 Worker 容错(Failover)流程

  11. 总结与思考


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 构造时根据平台环境选择 EpollNio 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 构造中同样选择 EpollNio 环境,初始化 workerGroup 线程池以及 callbackExecutorresponseFutureExecutor,并启动心跳扫描:

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(或其他注册中心)交互,完成以下工作:

  1. 注册当前 Master 节点:在 /dolphinscheduler/master 下创建临时节点并写入心跳信息

  2. 启动心跳线程:定期更新节点数据,避免节点失联

  3. 监听集群变化:订阅 /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 负责启动,其中包含三个部分:

  1. 恢复命令:查询所有未执行的 Command,将对应的 WorkflowExecuteRunnable 加入执行缓存与事件队列

  2. 事件循环WorkflowEventLooper 不断拉取 workflowEventQueue,根据事件类型调用不同的 Handler

  3. 任务执行器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();

其中 globalTaskDispatchLooperglobalTaskDispatchWaitingQueue 获取 DefaultTaskExecuteRunnable,调用 taskDispatcher.dispatch()

Message msg = taskDispatchRequest.convert2Command();
masterRpcClient.sendSyncCommand(host, msg, timeout);

7. 事件处理服务

事件处理分为两类:

  • Workflow 事件:如任务状态变更、流程阻塞等

  • Stream 事件:针对流式任务的自定义事件

EventExecuteService.start() 启动线程池,不断从 stateEventstaskEvents 队列消费:

workflowEventHandler(); // 提交到 workflowExecuteThreadPool
streamTaskEventHandler(); // 提交到 streamTaskExecuteThreadPool

8. 故障转移线程

failoverExecuteThread 周期检查 Master 与 Worker 节点的存活情况。对于失联节点,分 Master Failover 与 Worker Failover 两种场景:

8.1 Master Failover

当 Master 宕机,注册中心删除其节点,其他 Master 节点触发 failover:

failoverService.failoverServerWhenDown(serverHost, MASTER);

doFailoverMaster 会:

  1. 查询需要 Failover 的 ProcessInstance

  2. 对每个实例调用 processService.processNeedFailoverProcessInstances(processInstance),重新将任务写入 t_ds_command

8.2 Worker Failover

当 Worker 宕机:

failoverService.failoverServerWhenDown(workerHost, WORKER);

failoverWorker 会查询该 Worker 上执行中的 TaskInstance,并对每个未完成任务:

  1. 设置 state = NEED_FAULT_TOLERANCE

  2. 调用 taskInstanceDao.upsert 更新数据库

  3. 提交 TaskStateEventworkflowExecuteThreadPool,重新派发给其他 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 与消息队列为基础,开发者可自行跟进链路中遗漏的细节,并结合社区文档和源码仓库进一步学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GawynKing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值