别让大数据任务“互相等着死” ——聊聊任务依赖与 DAG 设计的江湖规矩
兄弟姐妹们,今天咱来聊一个在大数据平台里最容易把人搞破防的问题:任务依赖与 DAG 设计。
我见过太多人刚接触大数据调度系统的时候,张口闭口就一个想法:
“我这 20 个任务,随便排吧,反正跑起来就行。”
然后现实啪啪打脸——
任务死锁、每日延迟、数据不一致、补数像剥洋葱一样一层层回溯,甚至一个表延迟 8 小时,让上游中游下游同时骂娘。
那问题出在哪?
不在 SQL,不在 Spark,不在存储。很多时候死在任务依赖管理。
一、DAG 到底解决啥问题?别装了,说人话!
所谓 DAG(Directed Acyclic Graph)——有向无环图。
说人话就是:
- “我先跑谁,再跑谁”
- “谁依赖谁”
- “不能绕一圈最终依赖自己”
它解决的是核心大问题:
- 数据要有顺序
- 任务不能死循环
- 能可视化可追踪可补数
- 任务延迟可定位
你要是想象大数据任务是一排排多米诺骨牌,
DAG 的意义就在于:
谁先倒,谁后倒,不能倒回头。
二、为什么“依赖管理”是大数据的生死线?
举个真实案例:
你有个数据仓库:
- A 表:用户基础数据
- B 表:订单信息
- C 表:订单聚合日报
- D 表:数据分析结果报表
如果 B 依赖 A,C 依赖 B,D 依赖 C。
结果有人把 D 表给调到凌晨 2 点跑,还让 B 在 2 点半才完成。
于是 D 表利用的是前一天的老数据——
然后 BI 报表展示数据穿越回过去。
业务线狂骂:
“昨天数据怎么变回前天的?!”
这就是依赖顺序坏了,系统还能跑,但跑错了。
所以真实的坑是:
- 错数据比不产出还致命
- 延迟会传染整个链路
- 补数越补越乱
- 任务之间互相等死
我见过最恶心的情况:
400 条任务彼此依赖,竟然绕成了环,大家互相等,最终凌晨 3 点没有一条任务触发。
那叫一个壮观。
三、DAG 设计的三条“江湖规矩”
规矩 1:数据分层必须天然支持 DAG
最怕有人上来就一个肥 SQL,从 ODS 拉到 DW,再 join 维表,再算指标,然后存三份结果,最后写入 Kylin 立方。
兄弟,这叫自我毁灭。
数据层次应该是什么?
ODS → DWD → DWS → ADS
每层做一件事:
- ODS 保留原始宽表
- DWD 清洗、规范、建模
- DWS 主题聚合
- ADS 面向业务呈现
每层都是 DAG 的 Checkpoint,这样依赖天然分离。
规矩 2:一个任务干一件事,多任务组合复杂逻辑
新手会干这种事情:
“我要对账、聚合、指标计算一起算。”
导致任务过重、依赖不可拆、补数牵一发而动全身。
正确方式:
- 一个任务只产一份数据
- 多任务组合实现业务逻辑
- 依赖清晰、可重跑、可复用
简单粗暴一句话:
计算拆开写,成果少量存。
规矩 3:依赖必须显式写出来,不能靠猜
依赖不写清楚会怎样?
- 下游永远比上游早跑
- Spark 跑个寂寞
- 计算结果全是旧数据
Airflow / DolphinScheduler / Azkaban / Oozie
都支持显式依赖:
# Airflow 示例
from airflow import DAG
from airflow.operators.dummy_operator import DummyOperator
with DAG('demo_dag') as dag:
task_A = DummyOperator(task_id='A')
task_B = DummyOperator(task_id='B')
task_C = DummyOperator(task_id='C')
task_A >> task_B >> task_C # DAG: A -> B -> C
这就是 DAG 的灵魂:
明确告诉系统,顺序必须这样走。
四、再聊依赖管理中的“坑点”
坑 1:任务时间窗口对不上
有的人任务每天跑 2 点,有的人 4 点,还相互依赖。
这叫:时间对不上,永远跑不对
一个成熟平台必须:
- 上游完成时间可估
- 下游根据上游快照等候
- 用 event 触发更可靠
坑 2:依赖过深,让补数成为灾难
我见过数据补数补出 PTSD 的同事。
原因是链路太深:
A → B → C → D → E → F → G → H…
补 A 需要等全部链路反应,补出来还要
- 校验数据
- 回填
- 重跑报表
正确方式:
- 聚合层少
- 缓存层全
- 每层独立可重建
说白了:
可补性是 DAG 的终极价值。
坑 3:血缘关系不清晰
一个表被三十个下游依赖,
结构改动还不通知。
这一刀砍下去,下游全挂。
血缘图必须明确:
- 谁用我
- 我依赖谁
- 改动影响到谁
五、一个干净利落的 DAG 设计示例
我们用 Spark 模拟一个分层计算逻辑:
# DWD: 清洗日志
df_dwd = spark.sql("""
SELECT user_id, event_time, action
FROM ods_event_log
WHERE event_time >= date_sub(current_date(), 1)
""")
df_dwd.createOrReplaceTempView("dwd_event")
# DWS: 聚合
df_dws = spark.sql("""
SELECT user_id, COUNT(*) as action_cnt
FROM dwd_event
GROUP BY user_id
""")
df_dws.createOrReplaceTempView("dws_user_action")
# ADS: 产出报表
df_ads = spark.sql("""
SELECT u.user_id, a.action_cnt, u.level
FROM dws_user_action a
JOIN dwd_user_info u
ON a.user_id = u.user_id
""")
每一层都是 DAG 的节点。
六、那我们到底要怎么做?五条落地建议
- 依赖要写死,不靠时间猜
- 数据分层天然保护 DAG 结构
- 多任务组合,小任务原子化
- 每层都可以补数
- 血缘清晰、依赖透明
一句核心:
DAG 不仅是技术,更是工程治理。
七、最后说点感受:
做大数据的这几年,我越来越感觉到:
- 技术不是决定成败的关键
- 硬件算力也不是最大瓶颈
- 运营过程才最折磨人