3. 数据依赖核心概念
在 Airflow 中,DAG 并不是“一串按顺序执行的任务”这么简单。真正决定一个任务何时能跑、跑得对不对、补数时会不会踩坑的,是它背后的一整套依赖体系。很多初学者把依赖等同于 task_a >> task_b,结果在回填历史数据时发现任务提前执行、读到空分区、或者上游还没产出下游就跑了。要避免这些问题,必须把依赖拆成四个维度来看:任务依赖、数据依赖、时间依赖、触发规则。
一、核心原理:四类依赖各管什么
任务依赖描述的是任务之间的执行顺序,由 DAG 中的边(edge)表达,例如 extract >> transform >> load。它只保证“谁先谁后”,不保证“数据是否真的就绪”。数据依赖描述的是任务对具体数据分区、文件、表或外部数据集的依赖,例如“必须等 ods_orders 的 dt=2024-05-01 分区产出后才能跑”。时间依赖由 schedule、start_date、data_interval 共同决定,它回答的是“这个 DAG 在哪个逻辑时间点被触发”。触发规则则由 trigger_rule 控制,决定上游任务处于成功、失败、跳过等状态时,当前任务是否允许运行。
这四者不是并列孤立的,而是层层叠加:时间依赖决定 DAG Run 何时产生,任务依赖决定 Run 内部的任务顺序,数据依赖决定任务是否真的具备可处理的数据,触发规则决定异常状态下任务是否放行。理解这个叠加关系,是后面学习补数回填的前提。
二、方法流程:从 DAG 定义到依赖落地
在编写 DAG 时,建议按以下顺序梳理依赖,而不是先写任务再补依赖:
- 确定时间粒度:明确 DAG 是小时级、天级还是月级,设置
start_date与schedule,并理解data_interval_start与data_interval_end的含义。 - 画出任务依赖:用
>>或set_upstream/set_downstream建立任务顺序,避免出现环形依赖。 - 标注数据依赖:明确每个任务读取哪个分区、写入哪个分区;跨 DAG 依赖可用 Dataset(Airflow 2.4+)或
ExternalTaskSensor表达。 - 选择触发规则:默认
all_success,但清洗、告警、汇总等场景常需要all_done、one_success或none_failed_min_one_success。 - 验证回填行为:用
airflow dags backfill或 UI 的 Backfill 功能,观察逻辑日期与数据分区是否一致。
下面是一个同时体现时间依赖、任务依赖与数据依赖的 DAG 片段:
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta
default_args = {
"start_date": datetime(2024, 5, 1),
"retries": 2,
"retry_delay": timedelta(minutes=5),
}
with DAG(
dag_id="orders_daily",
schedule="0 2 * * *", # 时间依赖:每天 02:00 触发
default_args=default_args,
catchup=True,
) as dag:
def extract(ds, **ctx):
# 数据依赖:读取 ds 对应分区
print(f"extract partition dt={ds}")
def transform(ds, **ctx):
print(f"transform partition dt={ds}")
def load(ds, **ctx):
print(f"load partition dt={ds}")
t1 = PythonOperator(task_id="extract", python_callable=extract)
t2 = PythonOperator(task_id="transform", python_callable=transform)
t3 = PythonOperator(task_id="load", python_callable=load)
t1 >> t2 >> t3 # 任务依赖
三、案例场景:触发规则如何影响补数
假设某天 transform 失败,但 extract 成功。默认 all_success 下,load 不会执行,这是符合预期的。但如果业务要求“无论上游是否失败,都要执行告警任务”,就需要把告警任务的 trigger_rule 设为 all_done。再比如,多个上游分支中只要有一个成功就继续汇总,应使用 one_success。补数时如果错误地使用了 all_done,可能导致下游在数据不完整的情况下继续写入,造成脏数据。
| 触发规则 | 含义 | 典型场景 |
|---|---|---|
all_success |
所有上游成功才执行 | 默认,严格数据加工链 |
all_failed |
所有上游失败才执行 | 统一失败兜底处理 |
all_done |
所有上游结束即执行 | 告警、清理、审计 |
one_success |
任一上游成功即执行 | 多源汇总、择优取数 |
none_failed_min_one_success |
无失败且至少一个成功 | 允许跳过但不允许失败 |
四、误区与复盘
误区一:把任务依赖当成数据依赖。 t1 >> t2 只说明 t1 先跑,不代表 t1 产出的分区就是 t2 需要的分区。补数时若逻辑日期错位,t2 可能读到空数据。
误区二:忽略 data_interval。 Airflow 2.x 中 ds 表示数据区间起点,不是“今天”。回填 5 月 1 日的数据,实际处理的是 2024-05-01 这个逻辑分区,而不是执行当天的日期。
误区三:滥用 all_done。 它会让下游在上游失败时也执行,若下游没有幂等保护,容易产生重复或脏数据。
复盘建议:每次设计 DAG 时,用一句话描述“这个任务在什么时间、依赖哪些数据、上游什么状态下才允许运行”。如果这句话说不清楚,说明依赖设计还不完整。补数前先在小范围日期上验证,再全量回填。
掌握这四类依赖之后,你就能准确判断一个任务“为什么没跑”“为什么提前跑”“为什么补数补错了”。下一阶段我们会把这些概念直接应用到补数回填的实战操作中。