06调度值班室:海豚 DolphinScheduler目录
几百个任务,谁先谁后、几点开跑、挂了重试几次、昨天漏的怎么补——靠人记不住,只能交给一间值班室、一张时刻表、和一把只借一小时的钥匙。
厂区门口有间值班室,墙上钉一张时刻表:07:00 开泵、08:00 上工、09:00 出数。每个格子里站着一个工位,工位上有专用家伙什;值班长手里那把总钥匙不挂着随便拿,谁要动真东西,登记、借一小时、干完还回来。

一、先说人话

这一关的东西,平台上的名字叫 DolphinScheduler,日常都喊"海豚"。它不搬数据、不写 SQL、不算指标。它只干一件事:照时刻表把该干的活按顺序叫起来。

为什么非得有这么一间值班室?一个项目里就有 756 个任务摊在 10 台机器上;SUPP 那个场景 44 条工作流,42 条每天 08:00 一起开跑。谁等谁、几点开、失败重试几次、昨天漏了怎么补——这四件事人脑记不住,一张总表能记住。

而且这张表分三套厂区:生产(真正对外供数)、UAT(先试)、开发(本地验证)。三套厂区不是三个标签,是三套彼此不通的水电——资源、SQL 权限、发布权限全不一样。所以"开发厂区跑通了"这句话什么都不能证明:那儿连调度都是关着的。

先把这一关的词对一遍号,后面就不来回翻译了。

值班室
DolphinScheduler(海豚)——到点叫人、按顺序放行、失败重试、漏了补数
时刻表
调度周期(cron)+ 上线 / 下线——几点开跑、今天跑不跑
格子与箭头
工作流定义:节点 + 依赖(DAG)——谁先谁后
工位
Worker 分组 datax / seatunnel——这活排在哪台机器上干
工牌
t_ds_user / t_ds_session——你以谁的身份动手
总钥匙
临时管理员(apply-admin,1 小时)——能不能改真东西

下面这张图把这一关的四件东西摆在一起:左边一张时刻表,右边一条依赖链,底下那把钥匙。

值班室:一张时刻表、一条依赖链、一把总钥匙 值班时刻表 07:00 开泵 · 入湖 08:00 上工 · 加工 09:00 出数 · 同步 漏了哪一格,补哪一格 不是推到明天整条重跑 依赖链:谁等谁 A 入湖 B 加工 C 出数 支 · 页面 支 · 校验 C 变绿 ≠ 数据对 总钥匙:申请临时管理员,只有 1 小时 apply-admin → 动手 → cur-perm 读回确认 到点自动失效

左边那三格是"什么时候",右边那条链是"先干什么后干什么",中间用箭头咬死;底下那颗钥匙管的是"你凭什么能动手"。四件东西缺一件,值班室就成了墙上的一张废纸:只有时刻表没有依赖,下游会在上游还没出数的时候照样开跑;只有依赖没有钥匙,谁都能上去改两笔。

二、拆开看:每个零件是什么

2.1 三套厂区:同一套代码,三种待遇

像什么:同一个班组的活,在自家车间怎么试都行,进了客户现场连插头都不许自己换。厂区之间不是靠自觉,是靠制度把权限焊死。

其实是什么:海豚的环境分生产、UAT、开发三类,环境设置由系统管理员完成,要改就得联系平台组管理员——不是自己在页面上点一下能改的。三套厂区的差别不只是"数据不一样":用途、调度、能连哪些资源、能存什么 SQL 和同步作业,每一处都不一样。

🏭 三套厂区:连开关都不一样
差在哪生产环境UAT 环境开发环境
用途 正式上线、长期周期运行 集成验证测试、短期内周期运行 本地开发验证
调度开关 开启 开启 不开启
资源 LINK 所有 LINK ETL 全部 LINK;同步只授 UAT 写权限的 LINK 只授开发环境 LINK(无生产写权限);不授权同步 LINK
发布限制 管理员 / 临时管理员 同左 无限制
生产 · SQL 保存
禁 CREATE / DROP;禁读写 _sit、_uat、temp_db、default 这些测试环境表;import_db 只读
UAT · SQL 保存
禁 CREATE / DROP;只能写测试环境 Schema:_sit、_uat、temp_db、import_db、default
开发 · SQL 保存
无限制
生产 · 同步保存
禁读后缀为 _sit、_uat、temp_db、import_db、default 的库内表;写入不限制
UAT · 同步保存
读取不限制;只能往后缀 _sit、_uat 的 schema 同步数据
开发 · 同步保存
—(原文未列限制,只说了不授权同步 LINK)
真名
生产环境 / UAT 环境 / 开发环境(《项目多环境介绍》)
谁改
环境设置由系统管理员完成

看完这张表,"开发环境里先跑一遍"这句话的分量就清楚了:那儿调度是关的,只能手动点。手点能过,只说明脚本没错;明天早上七点会不会自己起来、起来之后连的是哪个库,开发厂区一个字都答不了。

注意最容易被这几条限制绊住的是"同步保存":生产厂区禁读测试 schema,UAT 厂区只能往 _sit、_uat 里写。所以一个同步任务在 UAT 里配得再顺,搬到生产也得把那两个后缀改回正式库——不改,保存这一关就过不去;真过去了,那是把测试数据往生产灌。

2.2 五个厂区编号:认数字,不认名字

像什么:厂门口的门牌,写得再漂亮也没用,真正认的是那串编号。走进隔壁厂区,你干的活全算别人的。

其实是什么:海豚里每个项目都是一个编码,一串纯数字。下面这五个是这套体系里真在用的,抄错一位,改的就是别人的厂区。

生产 BI_PRD
148405435832576
测试 BI_SIT
149223761565792
ETL 生产
136016656440288(ETL_日常离线计算)
ETL SIT
12302943659968
SUPP
12923899913280

这五个里,前两个是主厂区,中间两个是专门跑 ETL 的车间,最后一个是某个业务场景自己的院子(SUPP 那 44 条工作流就住在这儿)。

注意名字和环境的叫法不完全统一:文档里那三套厂房叫"生产 / UAT / 开发",而平台上这个测试项目的编码叫 BI_SIT,表名里也带 _sit 后缀。别拿"UAT"三个字母去猜项目,认编码。对不上的时候,以项目的数字编码和表名后缀为准。

2.3 五种工位:干不同的活,站不同的台

像什么:厂区里就五个工种。有的工位什么也不生产,只站在门口当个路标;有的工位必须接特定的机床;有的工位是把别人的活整段搬过来复用。

其实是什么:《数据实施规范》把节点类型定死了,一个工作流就是这五种工位拼起来的。

🧰 五个工位,各管一段
工位干什么为什么是它 / 怎么配
SHELL 开始节点、结束节点、纯中转 它不干活,只要有个节点把箭头连起来;配置全默认,脚本 echo 1
SQL 业务 SQL 本体 湖里所有 Spark SQL 只有一个入口,别的类型连不进去;数据源类型选 KYUUBI
SUB_PROCESS 子流程、模板 一段活被好几个工作流复用,改一处全体生效;参数必须评审,不塞私人参数或项目参数
DATAX 入湖:源库 → 数据湖 Hudi 入湖是"从别人的库里抽",源端压力小;Worker 分组 datax、环境名称 datax
SEATUNNEL 出湖入仓:Hudi → Doris(也支持别的源和目的) 出湖要分布式算,吃的是集群算力;Worker 分组 seatunnel、环境名称 seatunnel
真名
SHELL / SQL / SUB_PROCESS / DATAX / SEATUNNEL
出处
《数据实施规范》3.3 任务定义规范

这张表里藏着两个低级但很贵的错。第一个是把中转节点配成 SQL 节点:它什么也不查,却要占一个 Kyuubi 会话,白排队。第二个是 SUB_PROCESS 里写死参数:模板是给全厂复用的,你在模板里塞一个只对自己有意义的参数,下一个用模板的人就被你的参数带跑。

小窍门两个数据集成工位的分组名和环境名是成对的:DATAX 配 datax,SEATUNNEL 配 seatunnel。分组挑错不会报错,只会一直排队——活没丢,就是不上工。

2.4 参数放哪:全厂通用的挂墙上,只给子流程的才写在工作流上

📌 参数的两层楼

像什么:厂区大门口挂一块牌子,写着今天几号、走哪条线——所有人抬头都看得见。没人会把"今天几号"抄在自己工位的抽屉里。

其实是什么:参数分两层。跑哪一天(biz_date)、连哪个库这类全厂都要用的,放项目级参数;只有要往子流程里传值的时候,才在工作流上定义参数。

真名
项目级参数 / 工作流参数
规矩一
原则上所有 SQL 运行依赖的参数,都得在项目级参数里定义
规矩二
相同含义的参数不允许重复定义
规矩三
工作流参数原则上只在向 SUB_PROCESS 传数据时才定义

搞错了会怎样:同一个东西起两个名字,改的时候改了一个、漏了另一个。生产跑起来连的还是旧库,SQL 一句没报错,数全落在错的表里——这种错最难查,因为两处都"看起来对"。

2.5 发布一次,到底动了什么

这一段是这一关最硬的地方。下面不是笼统的流程,是本机脚本 add_seatunnel.py 真跑过的顺序:给一个工作流加两个 SeaTunnel 节点,从验身份一直到最后回读核对。

  1. 先验工牌。第一步 ds_identity.resolve_ds_session(项目编码),去 t_ds_user、t_ds_session 两张表里查这个人现在有没有有效会话,默认账号 liuliyin,拿到之后先断言用户名就是它。查不到不会回落成项目 owner——值班室不能因为刷不出你的卡,就自动借你项目经理的卡。
  2. 整份拆下来。GET /projects/{项目编码}/process-definition/{工作流},一次取回三样:processDefinition(工作流本体:名字、版本、超时、执行方式)、taskDefinitionList(所有节点)、processTaskRelationList(所有连线)。这个接口不接"只补一个节点",它要的是整份。
  3. 领新号。gen-task-codes 申请新节点的编号(genNum 填这次要几个);脚本还回头查一遍:数量对得上、号码没有重复。
  4. 整份交上去。PUT 同一个地址,body 里带 taskDefinitionJson(节点)、taskRelationJson(DAG 连线)、locations(每个节点的 x/y 坐标)、globalParams、timeout、executionType(SERIAL_WAIT)、tenantCode。少连线,节点全成孤岛;少坐标,所有节点叠成一坨。返回体里 code 必须等于 0——HTTP 200 不算过。
  5. 当场念回执。再 GET 一次,对版本号、节点数、依赖数,逐个节点打 taskType 和源表 / 目标表核对。不 GET 回读,就等于没验过——PUT 返回成功只说明服务器收下了,不说明它收的是你写的那一份。
  6. 发布,再启动。release 上线、start 起跑。动这一步之前要先 apply-admin 申请 1 小时临时管理员,事后用 cur-perm 读回:确认权限拿到了,也确认它会自己过期。
会出事 · 不回读就点发布PUT 之后看到 code=0,直接去点发布和启动。结果节点数少一个(新加的节点压根没进定义)、或者连线上少一条(新节点挂在那儿没人叫它),工作流照样天天"跑成功",只是有一段活从来没干过。最阴的一种:定义里还留着 _sit 的表名——正式定义引用了 SIT 运行对象,跑出来的是测试数据。
注意临时管理员只有 1 小时,而且申请理由要写具体场景,不能只写"下线"两个字——那是停服务的说法,会招审批。写成"哪个场景 + 调什么",例如"库存趋势场景 5 月 6 月数据异常调整重刷"这种。钥匙借出来是干活的,不是挂着的。
小窍门这套动作对同一份定义连着做两次,第二次一定会出事:脚本里有一句先查重——新节点的名字如果已经存在,直接停下来等人确认。手工操作也照这个来:先 GET 看一眼现在有几个节点、叫什么名字,再决定加不加。别靠"我记得没加过"。

2.6 状态怎么看:五个词,一句人话

像什么:值班室墙上的灯。绿灯只说明"这个工位没出事",不说明"这个工位今天做的活是对的"。

其实是什么:海豚给每个任务实例一个状态。翻来覆去就五个词,认全了,一半的误会就没了。

状态一句人话
RUNNING_EXECUTION正在转,还没出结果——先看它跑了多久,别催
SUCCESS跑完了、脚本没报错——只代表"没出错",不代表"数对"
FAILED报错停了——资源不够、上游失败、脚本本身报错
KILLED被人或被平台掐断——超时、手动停、资源被回收
WAITING在等上游,自己不动——上游还没成功,它就在门口排队

近 8 天的实测:45,571 个成功、296 个失败、529 个被杀,失败率 0.65%。失败根因集中在 SeaTunnel 心跳超时(OperationTimeoutException / operation-heartbeat-timeout),靠下游 retry_to_doris 补跑之后全部成功。所以值班看板上"红了一下又绿了",多半是这条链自己在补。

会出事 · 把 SUCCESS 当验收SUCCESS 是"这段脚本正常退出了",不是"这段脚本算对了"。上游少给一天数据、增量时间字段没更新、口径改过但没重跑——这三种情况下任务全是绿的,数全是错的。所以验数是另一道工序,不能拿状态灯代替。

三、串起来:一次完整的流程

把零件按一天的顺序排一遍,就是值班室真实的一天。前面三步是"上工之前",后面五步是"上工之后"。

  1. 先在开发厂区试。那儿调度是关的,只能手点。手点跑通只说明代码没错,不说明它会自己起来。
  2. 进 SIT 冒烟。交付测试的依据写得很死:SIT 环境的增量任务冒烟测试通过,才算可交付测试。
  3. 进 UAT 联调。UAT 只能写测试 schema、只能同步进 _sit / _uat;相关增量任务测试通过,才谈得上生产发布。
  4. 07:00 开泵。DATAX 节点(Worker 分组和环境名都是 datax)把源库当天的新数据搬进 Hudi 湖。
  5. 08:00 上工。SQL 节点(数据源类型 KYUUBI)沿依赖链一条条跑;SUPP 那 42 条工作流也在 08:00 集中开跑,所以这个点最容易看到 WAITING。
  6. 09:00 出数。SEATUNNEL 节点(Worker 分组和环境名都是 seatunnel)把湖里的结果同步进 Doris,页面和报表读的是 Doris,不读湖。
  7. 白天出岔子。先看状态:WAITING 是在等上游,RUNNING_EXECUTION 是在转,FAILED 是报错,KILLED 是被掐。SUCCESS 只说明脚本没报错,别再往下替它保证。
  8. 隔天补漏。昨天哪一格没跑成,补哪一格。别把整条链从头重跑一遍:结果表是整表覆盖写的,重跑会把下游已经算好的那份再盖一次。

这八步里,真正需要人盯的只有第 7 步和第 8 步。前面六步是制度在跑:厂区隔开、工位定死、参数收口、发布回读。制度跑得顺,值班室就安静;值班室天天响,八成是前面某一步被人抄了近路。

四、搞错了会怎样

值班室出的事,几乎都不是"任务挂了",而是"任务没挂,数不对"。挂了你至少知道要修;不挂,你要等业务方打电话来问"这个月怎么和上个月一模一样"。

会出事 · 依赖只认"上次成功",不认"今天跑没跑"这是真事故,不是假设。红线里的原话是:依赖检查只认上次成功、不认今天跑没跑——所以下游 08:00:05 就放行,上游 10 月 3 日 下线后仍天天搬旧数据。机制很直白:上游被下线之后,它没有"今天失败",它只是不跑了;而检查里没有"今天跑过没有"这一问,于是判定没事,下游照常开跑,读昨天那份表,一天一天往下搬。

像什么:值班表上写"等老王交班再开泵"。老王十月三号就调走了,可本子上他最后一次签字还是"正常"——于是没人再问一句"老王今天来了吗",泵每天照开。

注意 · 一台机器承接太多会排队实测现状:一个项目 756 个任务摊在 10 台 worker 上,其中 10.160.10.26 一台接了 218 个、排队 3.8 分钟,另外 9 台基本空闲。这不是报错,是慢慢来:任务到点没动,看着像挂了,其实在等窗口。

所以"卡住"的第一反应不该是改 SQL。先看状态是 WAITING 还是 RUNNING_EXECUTION,再看是不是排在这台忙机器上,最后才轮到怀疑脚本。顺序反了,SQL 改十遍也没用——活根本没开始跑。

小窍门值班三问,按顺序问:它跑了吗(状态)→ 它在哪跑(Worker 分组和排队)→ 它跑对了吗(验数)。前两问都在调度这一层能答,第三问调度答不了,得靠对账和一致性检查。

五、一句话记住

做对了调度只保证"按点跑",不保证"数对";数对要靠验数。

值班室能担保的是四件事:到点叫人、按顺序放行、挂了重试、漏了补上。它担保不了的是第五件:被叫起来的那个人,今天搬的是不是今天的数据。那件事归验数——入湖对账、湖仓一致性检查、表级监控,一条一条对出来的。

所以看这一关的正确姿势是:状态灯管"有没有跑",验数管"对不对"。两本账分开记,值班室才不会变成一间只会报"一切正常"的屋子。

事实来源:《项目多环境介绍》《数据实施规范》3.2–3.3 · 本机 add_seatunnel.py · 平台实测记录这一关只管"按点跑"下一关:主干管 SeaTunnel