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 模型,并初始化 bossGroupworkerGroup

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 线程池以及 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 负责启动,其中包含三个部分:

  • 恢复命令:查询所有未执行的 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();

其中 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 会:

  • 查询需要 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 更新数据库
  • 提交 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 与消息队列为基础,开发者可自行跟进链路中遗漏的细节,并结合社区文档和源码仓库进一步学习。

原文链接:https://blog.csdn.net/wen811651208/article/details/149231505

posted @ 2026-09-17 16:26  海豚调度  阅读(10)  评论(0)    收藏  举报