Apache DolphinScheduler 是一个分布式、易扩展且可视化的调度器,用于企业级大数据工作流的创建、调度及监控。Master 节点作为核心调度组件,负责接收任务指令、调度 DAG 工作流、分发任务至 Worker,并处理集群的容错和高可用机制。本篇文章聚焦于 DolphinScheduler 3.2.0 中 Master 启动的源码流程,通过梳理其启动顺序与核心模块,帮助读者快速理解 Master 服务的初始化与调度逻辑。
在 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 节点在启动后,会不断轮询并消费该表中的命令,随后真正恢复并执行工作流实例。
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");
}
}));
}
下面将逐一解析各步骤的源码细节与设计思路。
Master 节点内部通过 Netty 实现 RPC 通信,分为 RPC Server(监听来自 Worker 的心跳与状态上报)与 RPC Client(向 Worker 分发任务指令)。
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();
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);
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);
}
Master 通过
masterRegistryClient 与 ZooKeeper(或其他注册中心)交互,完成以下工作:
/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(),触发容错或资源清理。
核心调度引擎由
MasterSchedulerBootstrap 负责启动,其中包含三个部分:
WorkflowExecuteRunnable 加入执行缓存与事件队列WorkflowEventLooper 不断拉取
workflowEventQueue,根据事件类型调用不同的 HandlerMasterTaskExecutorBootstrap 启动线程池与队列,消费待派发任务并调用 RPC 分发给 Workerpublic synchronized void start() {
super.start(); // part1: 恢复并提交 Command
workflowEventLooper.start(); // part2: 事件循环
masterTaskExecutorBootstrap.start(); // part3: 派发任务
}
恢复逻辑示例:
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));
}
});
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;
}
MasterTaskExecutorBootstrap 负责三个队列的启动:
globalTaskDispatchLooper.start(); masterDelayTaskLooper.start(); asyncMasterTaskDelayLooper.start();
其中
globalTaskDispatchLooper 从
globalTaskDispatchWaitingQueue 获取
DefaultTaskExecuteRunnable,调用
taskDispatcher.dispatch():
Message msg = taskDispatchRequest.convert2Command(); masterRpcClient.sendSyncCommand(host, msg, timeout);
事件处理分为两类:
EventExecuteService.start() 启动线程池,不断从
stateEvents 与
taskEvents 队列消费:
workflowEventHandler(); // 提交到 workflowExecuteThreadPool streamTaskEventHandler(); // 提交到 streamTaskExecuteThreadPool
failoverExecuteThread 周期检查 Master 与 Worker 节点的存活情况。对于失联节点,分 Master Failover 与 Worker Failover 两种场景:
当 Master 宕机,注册中心删除其节点,其他 Master 节点触发 failover:
failoverService.failoverServerWhenDown(serverHost, MASTER);
doFailoverMaster 会:
processService.processNeedFailoverProcessInstances(processInstance),重新将任务写入
t_ds_command当 Worker 宕机:
failoverService.failoverServerWhenDown(workerHost, WORKER);
failoverWorker 会查询该 Worker 上执行中的
TaskInstance,并对每个未完成任务:
state = NEED_FAULT_TOLERANCEtaskInstanceDao.upsert 更新数据库TaskStateEvent 到
workflowExecuteThreadPool,重新派发给其他 Worker除了手动触发,Master 还使用 Quartz 实现定时调度功能。
SchedulerApi 注入
org.quartz.Scheduler 对象:
@Override
public void start() throws SchedulerException {
scheduler.start();
}
Quartz Scheduler 被用于周期性触发定时任务,例如流程依赖、定时工作流调度等。
本文详细梳理了 DolphinScheduler 3.2.0 中 Master 节点启动的全流程:从手动写入 Command,到 MasterServer 初始化、RPC 服务搭建、插件加载、注册中心接入,到核心调度引擎与事件处理,再到高可用的故障转移、Quartz 定时调度。Master 的设计展现了模块化与解耦:
在源码层面,核心逻辑均以 CRUD 与消息队列为基础,开发者可自行跟进链路中遗漏的细节,并结合社区文档和源码仓库进一步学习。
原文链接: https://blog.csdn.net/wen811651208/article/details/149231505
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70014275/viewspace-3182798/ ,如需转载,请注明出处,否则将追究法律责任。