先说结论:如果你手里攒了一堆定时跑批的活,不管是SQL脚本、Shell任务还是Spark/Flink任务,正缺一个能把它们统一管起来、能看依赖、能告警、能补数的平台,那DolphinScheduler大概是现阶段性价比最高的选择之一。我去年把一个部门七八台机器上的crontab全部迁到了DolphinScheduler上,从部署到上线跑完第一个工作流,前后大概花了一个周末,之后维护成本直线下降。这篇文章就把这套实践过程完整记录下来,覆盖部署、建工作流、配置告警、参数传递、踩坑排错,需要的朋友可以直接照着抄。
1. 调度系统选型与整体设计思路
1.1 为什么最终选了DolphinScheduler
先说说为什么不是其他方案。当时团队里跑批脚本的现状是这样的:每个数据开发各写各的crontab,时间规则自己定,脚本散落在不同机器上,依赖关系全靠口头约定。最痛的一次是凌晨2点A任务挂了,B任务看不到任何感知,照样到点跑,结果下游报表数据缺了一大块,第二天早上业务方找过来才知道出了问题。
这种场景下,我要的不是一个能"定时执行命令"的工具,而是一个能把所有任务收拢起来、画清楚依赖关系、出事能自动告警、事后能补数的调度平台。市面上的开源方案对比了一圈:
- Airflow:调度能力很强,DAG表达丰富,但Python技术栈在团队里推广成本高,而且安装部署、底层元数据库维护都不算省心。
- xxl-job:轻量,适合单任务秒级调度,但对"工作流"这种带依赖关系、带DAG编排的场景支持偏弱,更多是单点任务调度。
- 自研调度器:想清楚就知道不合适,调度平台的难点从来不是"定时触发",而是高可用、失败重试、分布式执行、日志收集、权限隔离,这些自己写轮子没有几个月下不来。
DolphinScheduler的切入点刚好卡在中间:它能编排DAG工作流,支持Shell、SQL、Spark、Flink、HTTP等丰富任务类型,后端用ZooKeeper做分布式协调,天然支持集群模式扩展,部署方式也灵活,单机可以跑,集群也能扛住生产压力。而且它对Java技术栈团队非常友好,后端、前端都有完整的管理界面,开发同学不需要学Python,只需要在页面上拖拖拽拽,学习成本比Airflow低很多。
1.2 核心概念:工作流、任务、租户、队列
想把DolphinScheduler用明白,先要把几个核心概念搞清楚,这些概念在前端界面上反复出现,理解了之后整个系统就通了大半。
工作流定义:可以理解为一张流程图,把多个任务节点用连线串起来。比如"数据同步"任务完成后执行"数据清洗",清洗完再触发"指标计算",这就是一个包含三个节点的工作流。工作流定义是一等公民,可以配置定时调度,也可以手动触发。
任务节点:工作流里的每一个环节。DolphinScheduler内置了非常多的任务类型,常见的有Shell、SQL、存储过程、HTTP、Spark、Flink、Python、数据同步(DataX)等。任务节点之间可以设置依赖关系,A节点成功后才允许B节点启动,也可以设条件分支,根据上游的输出决定走哪条分支。
租户:这是DolphinScheduler里一个容易被新手忽略但很重要的概念。租户对应操作系统里的一个Linux用户。Worker节点执行任务时,会以这个租户身份去执行Shell命令,脚本里产生的文件、日志的属主都是这个租户。换句话说,租户就是任务运行时的"身份",它决定了任务能访问哪些操作系统资源、文件权限边界在哪里。集群部署时,租户对应的系统用户必须在每台Worker机器上都存在,否则任务会报错。
队列:队列对应的是资源队列,在数值计算场景下一般指Yarn队列。如果你的任务要提交到Yarn上跑Spark或者Flink,需要指定队列名,用来做资源隔离和配额管理。如果只是跑Shell、SQL这类轻量任务,队列不是必须的。
1.3 整体架构与核心组件
DolphinScheduler的实际部署会拆成几个关键服务,理解它们各司其职,后面排查问题会快很多:
- MasterServer:工作流的大脑,负责任务调度、工作流实例的启动、停止、故障转移。它会从ZooKeeper上监听Worker的注册状态,把要执行的任务分配给合适的Worker。
- WorkerServer:真正干活的节点,负责执行任务,把执行日志写回数据库/文件系统。可以横向扩展,多台Worker天然负载均衡。
- ApiServer:后端接口服务,前端页面所有操作最终都调它,也负责把任务定义、调度信息持久化到数据库。
- AlertServer:告警服务,监听数据库里的告警事件,对接邮件、Webhook、钉钉等渠道。
- ZooKeeper:分布式协调器,Master和Worker通过ZK做选举、心跳、注册发现,是集群高可用的基石。
- 数据库:核心元数据存储,存工作流定义、任务实例、调度时间、告警记录等所有业务数据。
单机部署时这些服务都跑在一台机器上,但架构和集群模式完全一致,后续要扩机器,只需要加Worker节点就行,非常平滑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 单机部署完整过程
2.1 环境准备
我用的是DolphinScheduler 3.2.x版本,对应JDK要求是1.8+,数据库支持MySQL和PostgreSQL,我选了MySQL 8.0。操作系统是CentOS 7.9。先确认Java环境:
bash复制java -version
# openjdk version "1.8.0_362"
如果没有安装JDK,直接用yum装一个:
bash复制yum install -y java-1.8.0-openjdk.x86_64
接着下载安装包。从官方镜像源拉对应的release包即可,解压到 /opt/dolphinscheduler。我习惯把安装目录统一放在 /opt 下,方便后面写环境变量:
bash复制mkdir -p /opt
tar -zxvf apache-dolphinscheduler-3.2.x-bin.tar.gz -C /opt
mv /opt/apache-dolphinscheduler-3.2.x-bin /opt/dolphinscheduler
2.2 初始化数据库
在MySQL里先建好库和账号。DolphinScheduler要求库的编码是utf8mb4,因为工作流实例、任务日志这些字段都可能存中文,utf8mb4最稳妥:
sql复制CREATE DATABASE dolphinscheduler DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'dolphinscheduler'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON dolphinscheduler.* TO 'dolphinscheduler'@'%';
FLUSH PRIVILEGES;
初始化表结构需要用官方提供的脚本。脚本在解压目录的 sql/dolphinscheduler_mysql.sql,直接执行即可:
bash复制mysql -h127.0.0.1 -udolphinscheduler -p dolphinscheduler < /opt/dolphinscheduler/sql/dolphinscheduler_mysql.sql
初始化完成后可以进到库里看一眼,表会非常多,核心的表有 t_ds_process_definition(工作流定义)、t_ds_process_instance(工作流实例)、t_ds_task_instance(任务实例)、t_ds_schedules(定时调度)、t_ds_user(用户)、t_ds_alert(告警记录)。
注意:如果你用的是MySQL 8.0+,驱动必须手动放进去,否则后面服务启动会报找不到驱动。把mysql-connector-j的jar包放到
/opt/dolphinscheduler/tools/libs目录下,同时api-server/libs和worker-server/libs下也需要各放一份。我在这里卡了半小时,就是因为只放到了一处。
2.3 修改配置并启动
配置集中在两个文件里:bin/env/install_env.sh 和 bin/env/dolphinscheduler_env.sh。
install_env.sh 里主要配置部署方式。单机实践时,把各服务IP都配成 127.0.0.1 即可:
bash复制ips="127.0.0.1"
masters="127.0.0.1"
workers="127.0.0.1:default"
alertServer="127.0.0.1"
apiServers="127.0.0.1"
dolphinscheduler_env.sh 里配置Java环境变量和数据库连接信息:
bash复制export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
export DATABASE=mysql
export SPRING_PROFILES_ACTIVE=mysql
export SPRING_DATASOURCE_URL="jdbc:mysql://127.0.0.1:3306/dolphinscheduler?useUnicode=true&characterEncoding=UTF-8"
export SPRING_DATASOURCE_USERNAME=dolphinscheduler
export SPRING_DATASOURCE_PASSWORD=your_password
配置好后,先注册ZooKeeper所需的信息,再启动:
bash复制# 第一次部署需要创建数据库表和基础数据,这一步会调用前一步初始化好的库
sh /opt/dolphinscheduler/tools/bin/create-dolphinscheduler.sh
# 启动所有服务
sh /opt/dolphinscheduler/bin/install.sh
sh /opt/dolphinscheduler/bin/start-all.sh
启动完成后,用 jps 看看进程是否都在:
bash复制jps
# MasterServer
# WorkerServer
# ApiApplicationServer
# AlertServer
# ZooKeeper
看到这5个进程都在,说明部署成功。前端默认端口是 12345,浏览器访问 http://192.168.x.x:12345,用初始管理员账号 admin / dolphinscheduler123 登录。
2.4 部署验证和基本配置
登录进控制台后,第一件事是创建租户。在"安全中心 -> 租户管理"里新增租户,租户编码填一个Linux系统里真实存在的用户,比如 dolphin。然后在服务器上创建这个用户:
bash复制useradd dolphin
创建完租户后,还需要在"安全中心 -> 用户管理"里给admin用户绑定租户,否则创建项目、执行任务的时候会提示没有租户权限。这一步如果不做,我遇到的情况是工作流定义能建,但一运行就报"创建租户失败"之类的错误,引导信息不明显,很容易绕圈子。
3. 创建第一个完整工作流
3.1 项目、工作流与任务节点的落地
在DolphinScheduler里,项目是资源隔离的单位,一个项目下可以有多个工作流定义。项目命名建议和业务线对应,比如 data_warehouse_daily,和数仓分层设计对齐。
创建完项目后,进入"工作流定义"页面,点击"创建工作流",就能进入DAG绘制画布。左侧是任务类型面板,有SHELL、SQL、SUB_PROCESS等节点。我第一个实践工作流模拟的是一个离线报表的日常数据生产链路,包含三步:
- 节点A(数据同步):SHELL任务,模拟从业务库抽取数据到数仓ODS层。
- 节点B(数据清洗):SHELL任务,依赖A,对ODS数据做清洗落DWD。
- 节点C(指标计算):SHELL任务,依赖B,计算报表指标落ADS层。
拖三个SHELL节点到画布上,分别填写任务名称。任务脚本我用了最简单的示例:
bash复制# 节点A
echo "start sync data at $(date '+%Y-%m-%d %H:%M:%S')"
sleep 5
echo "sync data successfully"
保存工作流定义时,DolphinScheduler会要求设置租户。这个租户一定要选择刚才创建好的租户,否则任务真正执行时会以错误用户身份运行,Shell脚本里如果有写文件操作,会出现Permission denied。
保存后画布上三个节点默认是并列的,还没有依赖关系。我从A节点的输出端口拖一条线连到B,再从B连到C,这样就形成了A -> B -> C的有向无环图。DolphinScheduler的调度模式就是依靠DAG表达依赖,上游节点成功之后才触发下游节点启动,任何一环失败,下游不会执行。
3.2 任务之间的参数传递
当你把不同任务串成一个工作流之后,很快会遇到一个刚需:下游任务要用上游任务的输出结果。比如节点A算出了一个日期分区,节点B要用这个日期去查Hive表。DolphinScheduler提供了参数传递机制,通过setGlobal、setOut变量来打通。
我习惯的做法是:在节点A的Shell脚本里,把需要传递的值写成固定格式输出:
bash复制echo '{"target_date":"20250101"}' > /tmp/target_date.json
然后在节点B的脚本里拼接读取。但其实DolphinScheduler支持更原生的参数传递:上游任务可以通过 echo "${setValue(参数名)=参数值}" 的方式输出,下游任务直接用 ${参数名} 引用。
我的实际用法是这样的,节点A脚本:
bash复制echo "start generate date string"
target_date=$(date -d "yesterday" '+%Y%m%d')
echo "${setValue(target_date)=${target_date}}"
节点B脚本直接引用:
bash复制echo "target_date is ${target_date}"
这里有个关键点:参数传递默认是"全局"还是"局部"需要看工作流定义里配置。我建议在画布右上角的"工作流定义"设置里,把"全局参数"配置好,节点里的变量如果和全局参数同名,局部优先。生产环境里日期参数是最高频的变量,全局定义一套 bizdate、last_date 这类参数,所有节点统一使用,能避免很多手动改日期的低级错误。
注意:参数传递依赖任务实例的日志解析。如果上游任务脚本执行太快,还没来得及输出参数,下游就开始跑了,可能拿到空值。稳妥做法是在上游脚本里输出参数之后加一秒钟的sleep,把时序稳住。
3.3 定时调度与周期设置
工作流定义保存之后,需要上线并设置定时调度。在"工作流定义"页面点击"上线",然后进入"定时"配置。
定时规则用Cron表达式。如果你没写过Cron,DolphinScheduler的界面上有可视化Cron生成器,支持每天、每周、每月的快捷配置。比如每天凌晨2点跑一次,Cron表达式是 0 0 2 * * ? *。
定时配置里还有一个非常容易踩坑的设置:"超时告警"和"失败重试"。这两个建议在第一次创建任务时就配好。失败重试次数可以设置2-3次,每次间隔5分钟,对于依赖外部系统偶尔抖动的任务,这个机制能自动扛过去。超时时间建议根据任务预估时长设置一个上限,比如同步任务预计30分钟,超时时间设45分钟,一旦卡死,系统会自动把实例标记为超时失败,避免一直占用资源。
配置完定时之后,记得在"定时管理"里确认调度时间已经生成。有一个常见的坑是:配置了定时但工作流没有上线,调度永远不会触发;或者上线了但时间设置的是UTC时区,导致触发时间差8小时。DolphinScheduler的调度时间总体走的是服务端时区,如果服务器是UTC,Cron表达式里的"每天2点"实际是北京时间早上10点。我第一套环境就因为这个原因,设置凌晨2点跑批,结果每天上午10点才跑,差点漏了早报数据。解决办法是确保服务器时区是 Asia/Shanghai:
bash复制timedatectl set-timezone Asia/Shanghai
3.4 邮件告警配置
告警是调度平台最重要的能力之一,没有告警的调度平台等于裸奔。DolphinScheduler的邮件告警配置需要改两处:告警服务器配置和告警组/告警实例。
告警服务器的配置文件在 alert-server/conf/alert.properties,需要打开 mail.server.host、mail.server.port、mail.sender、mail.passwd、mail.starttls.enable 等配置项。我用的是公司邮箱的SMTP服务,配置示例:
properties复制mail.server.host=smtp.example.com
mail.server.port=465
mail.server.username=alerts@example.com
mail.server.passwd=your_smtp_password
mail.sender=alerts@example.com
mail.server.enable-ssl=true
mail.smtp.ssl.enable=true
配好后重启AlertServer:
bash复制sh /opt/dolphinscheduler/bin/stop-all.sh
sh /opt/dolphinscheduler/bin/start-all.sh
然后在"安全中心 -> 告警组管理"里新建告警组,选择邮件类型,填接收人的邮箱地址。
最后,在工作流定义的节点配置里,把"失败告警"的告警组关联上。这样节点失败触发重试仍然失败后,告警服务会往邮箱发送失败通知,通知内容包括任务实例ID、工作流实例ID、失败原因日志片段,直接点开就知道问题在哪。
4. 常见问题与排查技巧
4.1 部署启动阶段的高频问题
MasterServer和WorkerServer进程启动后立刻退出。这个现象我遇到两次,一次是数据库驱动缺失,另一次是MySQL的dolphinscheduler库不是utf8mb4编码导致表创建失败。排查办法是看日志,日志路径在各自服务目录的 logs/ 下面,重点看 master-server.log 和 worker-server.log 最后的堆栈信息。如果没有日志输出,先确认bin/env/install_env.sh里的IP配置是否都改成了实际IP,而不是默认的127.0.0.1,包括masters、workers、apiServers三部分。
ZooKeeper连接失败。部署脚本默认会内嵌启动一个ZooKeeper,但如果本机已经有一个ZK实例,端口2181被占用,DolphinScheduler的注册中心就起不来。这时优先检查2181端口占用情况:
bash复制ss -lntp | grep 2181
解决办法:要么停掉已有ZK,要么在dolphinscheduler_env.sh里把REGISTRY_ZOOKEEPER_CONNECT_STRING改成已有的ZK地址。
前端页面能打开,但登录后提示异常。这种情况多半是ApiServer连不上数据库,或者数据库连接池配置里密码错误。检查api-server/conf/application.yaml里的数据源配置,同时确认MySQL的max_allowed_packet参数足够大,否则后续创建工作流时一旦定义较长,可能出现"PacketTooBigException"。
4.2 任务执行失败排查
任务执行失败是日常使用中最常见的问题。DolphinScheduler的优势在于日志非常完备,几乎不需要到处找日志。在"工作流实例"页面点击某一个实例,再点具体的任务节点,右侧会弹出"查看日志"入口,里面能看到Worker节点上完整的执行日志。
我总结的排查套路是这样的:
- 先看任务实例的退出码。DolphinScheduler任务实例界面上有
退出码字段。如果是0,说明Shell命令本身执行成功了,但可能业务逻辑不对,查脚本输出即可;如果是非0,大概率是脚本本身报错。 - 再看日志里的Exception关键字。从日志尾部往上翻,找
Caused by。比如我当时一个SQL任务失败,日志里是com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure,明显是网络或数据库连接问题。 - 确认租户身份问题。如果日志里出现
Permission denied或者cannot find user,基本可以断定是租户对应的操作系统用户不存在,或者Worker机器上没建这个用户。在每台Worker机器上都执行一次useradd,重新跑任务即可。
4.3 调度时间不对与定时漂移
时区问题前面提到过,这里再展开说一个更隐蔽的坑:DolphinScheduler的Cron表达式默认是7位格式,最后一位是年(可选)。如果你从其他地方拷贝了一个6位的Cron表达式粘进去,可能被解析成完全不同的含义。比如 0 0 2 * * ? 表示每天2点触发,但 0 0 2 * * 这种6位写法直接粘到界面上,可能被解析为秒级循环。DolphinScheduler界面上的Cron生成器会实时展示下次触发时间,配置定时后务必人工核对"下次执行时间"那一行的预期时间对不对。
另外,调度时间偏移还有一种典型场景:服务器时间是正确的,但容器或虚拟机与宿主机时间不一致。如果发现所有任务都比预期时间晚执行,先检查宿主机和容器的时间同步情况,再统一用NTP对时。
4.4 参数传递失效的常见原因
参数传递失效一般有三种情况:
- 参数名拼写不一致。上游设置的是
date,下游引用的是target_date,自然拿不到。建议在画布右上角定义全局参数,用统一的命名规范。 - 上游任务未"提交"参数。在Shell任务中,只有通过
echo "${setValue(参数名)=参数值}"这种形式输出的参数才会被系统捕获,普通的export或echo不会生效。我一开始以为脚本里设了环境变量就能传下去,结果下游拿到的全是空。 - 依赖关系顺序错误。参数传递依赖任务执行的有序性,如果两个任务没有设置依赖关系,只是画在同一个画布上呈并行状态,上游还没跑完下游就去读参数,必然报错。
5. 从实践反推:调度平台设计要点
5.1 自动化调度系统应该具备的核心能力
把DolphinScheduler跑顺之后,反过来思考"自动化调度系统如何设计"这个问题,就清晰了很多。一个合格的调度系统,绝不只是"到点执行命令"这么简单,它至少要具备以下能力:
可靠性:任务执行中进程挂了怎么办?DolphinScheduler的Master节点通过ZK做选举,Master挂了能在秒级自动切换;Worker宕机后,正在执行的任务实例会被其他Worker重新拉起。这套机制本质上是"分布式状态管理",调度系统必须保证任务状态不丢失、不重复。
可观测性:什么时候跑的、跑了多久、成功还是失败、失败在哪里。DolphinScheduler把工作流实例、任务实例、日志全部持久化,这对排查问题实在太重要了。自研调度平台如果不从第一天就设计好日志和实例数据的留存,后面等于在黑洞里做运维。
失败处理:重试机制、告警机制、暂停/恢复/重跑。DolphiScheduler的重试做到了任务级,单个任务失败后自动重试,不用整个工作流重来。补数功能更是解决了大批量离线数据处理时"指定日期区间强制重跑"的痛点,这在crontab时代是根本无法想象的操作,只能手动改日期反复执行脚本。
5.2 权限模型与多团队协作
生产环境里,调度平台一般会被多个团队共用。DolphinScheduler的权限模型是用户-租户-项目三层:用户属于某个租户,租户映射操作系统用户,项目内有工作流、数据源、告警组等资源。实际落地时,建议每个业务线建独立项目,项目内成员按角色分配权限,比如只有负责人有"发布上线"的权限,普通开发只能"编辑"。
这块还有一个容易被忽视的点:工作流的"上线"操作一定要收敛权限。DolphinScheduler中,编辑中的工作流不会触发定时,只有上线后才生效。如果在生产环境里每个人都能随便上线,一个误操作可能把测试工作流推到线上。我们实践中由核心管理员统一负责"上线"环节,其他同学有编辑权限即可。
5.3 扩展实践:从Shell任务到数据任务
跑通Shell工作流只是第一步。DolphinScheduler真正的威力在于内置了丰富的数据任务组件。我后来把团队的Hive离线数仓任务全部接了上来,用的是SQL任务节点和Spark任务节点。
SQL任务节点可以直接配置数据源,MySQL、PostgreSQL、Hive、ClickHouse等都能通过数据源中心统一管理。之前写脚本里硬编码的JDBC连接字符串,现在全部收敛到"数据源中心",安全性和可维护性都提升了一大截。
Spark任务节点的用法更简单,填好任务的启动脚本,DolphinScheduler会负责把任务提交到Yarn集群,同时监控Yarn上application的运行状态,状态回传到任务实例。相比在Shell里手动 spark-submit 然后自己去解析日志,这种集成能省掉大量重复劳动。
6. 运维落地的一些额外建议
如果用DolphinScheduler跑生产调度,有几件事是我不建议跳过的。
数据备份:DolphinScheduler的元数据数据库是整个平台的灵魂,工作流定义、调度信息、告警记录全在里面。我每周会对 dolphinscheduler 库做一次全量mysqldump,保留最近4周备份。别等库被误删了才想起备份这件事。
节点日志轮转:Worker节点上的任务执行日志会持续增长,DolphinScheduler默认有日志保留策略,但如果你是刚刚部署自己用,最好确认一下 worker-server/conf/application.yaml 里的日志保留时间,避免磁盘被日志撑爆。
服务进程守护:单机部署时,如果服务器重启,DolphinScheduler的服务不会自动启动。我写了一个简单的systemd脚本,把 start-all.sh 注册成开机自启服务。如果是集群部署,建议用类似Supervisor的工具守护各服务进程。
版本升级前先做全量演练:DolphinScheduler的版本迭代速度不慢,升级前一定要在测试环境从头跑一遍完整流程,重点是数据库表结构变更是否自动执行、老工作流定义是否兼容。我遇到过小版本升级后,老的工作流定义全部变成"下线"状态,原因就是升级脚本没把状态字段迁移过来,那次着实折腾了一段时间。
7. 写在最后的实践经验
把公司调度体系从crontab迁移到DolphinScheduler之后,最大的感受倒不是"自动化"本身,而是"规范化"。以前数据任务跑挂了靠运气发现,现在失败有告警、排查有日志、恢复有重跑;以前脚本依赖关系画在纸上,现在DAG图一打开一目了然。
如果你还在crontab阶段,我的建议是从一个小场景开始切入,比如挑一条最常用的每日报表链路,先跑通一个三节点的工作流,再逐步扩大范围。DolphinScheduler的学习曲线并不陡峭,手把手把第一套环境搭起来,之后很多东西都是水到渠成的事。最后再分享一个我自己的习惯:每建一个新工作流,第一条Shell任务一定是先输出当前时间、目标日期和关键参数,这样即使任务失败,日志第一行就能确认调度参数有没有传错。这个习惯帮我在排查问题时省下了大量时间。
