未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用范围与待维护事项
本文主要按 Airflow 2.2+ 的 cron 数据区间调度模型讲解,不代表所有 2.x 小版本或 Airflow 3.x 均兼容;execution_date、next_ds、传统 SLA 和补跑命令须按部署版本核对,本文未提供环境实测记录。每天 02:00 的区间并非前一自然日,调度就绪也不等于任务准时启动。代码为片段,需补齐导入、函数与 provider,统一数据库时间字段和查询边界的时区;历史重跑前须验证幂等及重复写入处理。美国东部时区还需考虑夏令时。
调度策略与时间管理
abstract 本文核心内容
- cron 表达式完整解析
execution_date的真实含义(很多人理解错了)- 数据区间(data_interval_start/end)的概念
- 时区配置与常见陷阱
- SLA 超时监控配置
cron 表达式详解
cron 表达式是 5 个字段,用空格分隔:
┌───────────── 分钟 (0-59)
│ ┌─────────── 小时 (0-23)
│ │ ┌───────── 日期 (1-31)
│ │ │ ┌─────── 月份 (1-12)
│ │ │ │ ┌───── 星期几 (0-7,0和7都表示周日)
│ │ │ │ │
* * * * *
常用 cron 表达式示例
| 表达式 | 执行时间 | 数据工程场景 |
|---|---|---|
0 2 * * * |
每天凌晨 2 点 | 每日全量 ETL |
0 */6 * * * |
每 6 小时 | 6 小时增量同步 |
0 2 * * 1-5 |
工作日凌晨 2 点 | 只处理工作日数据 |
0 2 1 * * |
每月 1 日凌晨 2 点 | 月报汇总 |
30 1 * * * |
每天凌晨 1:30 | 分钟偏移,避免整点拥堵 |
*/15 * * * * |
每 15 分钟 | 实时性要求高的数据同步 |
0 8 * * 1 |
每周一上午 8 点 | 周报推送 |
0 0 1 1 * |
每年 1 月 1 日 | 年报汇总 |
cron 特殊字符
| 字符 | 含义 | 示例 |
|---|---|---|
* |
任意值 | * * * * * = 每分钟 |
, |
枚举多个值 | 0 2,14 * * * = 凌晨 2 点和下午 2 点 |
- |
范围 | 0 2 * * 1-5 = 周一到周五 |
/ |
步长 | */5 * * * * = 每 5 分钟 |
Airflow 预设别名
| 别名 | 等价 cron | 含义 |
|---|---|---|
@once |
- | 只运行一次 |
@hourly |
0 * * * * |
每小时 |
@daily |
0 0 * * * |
每天,按 DAG 时区的零点划分区间;默认时区配置为 UTC 且 DAG 未指定其他时区时为 UTC 零点 |
@weekly |
0 0 * * 0 |
每周 |
@monthly |
0 0 1 * * |
每月 |
@yearly |
0 0 1 1 * |
每年 |
None |
- | 不自动调度 |
tip 避免整点拥堵
如果你有多个 DAG 都在凌晨 2 点运行,它们会同时抢占 Worker 资源,导致相互等待。
建议给不同 DAG 加 5-15 分钟的偏移:0 2 * * *、5 2 * * *、15 2 * * *
execution_date 的真实含义
这是 Airflow 中最容易误解的概念,理解错了会导致数据处理逻辑的 bug。
常见误解
很多人以为:execution_date = 2024-01-15 表示"这个 DAG 是在 2024-01-15 运行的"。
这是错的。
正确理解
execution_date 是旧名称,对应逻辑日期(logical_date),不是实际执行时间;在本文讨论的 cron 数据区间调度中,它等于数据区间的开始时间,其他 timetable 不宜直接套用这一等式。
用一个例子说明:
DAG 配置:
schedule_interval = "0 2 * * *"(每天凌晨 2 点)
start_date = 2024-01-01
以下按 cron 数据区间调度列出边界,时间均使用同一 DAG 时区;实际启动还受调度器及资源可用性影响。
| execution_date(逻辑日期) | 最早可调度时间 | 数据区间(左闭右开) |
|---|---|---|
| 2024-01-01 02:00 | 2024-01-02 02:00 | [2024-01-01 02:00, 2024-01-02 02:00) |
| 2024-01-02 02:00 | 2024-01-03 02:00 | [2024-01-02 02:00, 2024-01-03 02:00) |
| 2024-01-14 02:00 | 2024-01-15 02:00 | [2024-01-14 02:00, 2024-01-15 02:00) |
关键规律:
- 在 cron 数据区间调度中,
execution_date= 数据区间的开始时间 - 数据区间结束后才具备调度条件;实际执行可能因暂停、资源排队等延后
- 对于
@daily的计划运行,数据区间通常对应 DAG 时区的一个自然日;任务是否处理该日数据,仍取决于查询逻辑和数据字段时区
因此,对于上述区间在 1 月 15 日凌晨 2 点结束的计划运行,{{ ds }} 的值是 2024-01-14,不是 2024-01-15;它由逻辑日期决定,不能仅凭实际运行时刻推断,也不能仅凭 ds 认定区间覆盖前一完整自然日。
def process_data(**context):
ds = context["ds"]
# 当前实际时间是 2024-01-15 02:00
# ds = '2024-01-14'(逻辑日期的日期部分;实际处理范围仍需看查询边界)
print(f"处理日期:{ds}")
note 为什么这样设计?
对于 @daily 的 cron 数据区间调度,1 月 15 日零点之后可处理 1 月 14 日的完整区间;但每天 02:00 的 cron 区间是前一天 02:00 到当天 02:00,并非前一自然日。
本文讨论的 cron 数据区间调度模型基于"处理一个已经完成的时间区间的数据",不代表所有 timetable。
数据区间概念
Airflow 2.2+ 引入了更清晰的 数据区间(Data Interval) 概念,用于明确处理范围;它并不替代逻辑日期这一概念,execution_date 对应的新名称是 logical_date。
| 变量 | 含义 | 示例(@daily 每日调度) |
|---|---|---|
data_interval_start |
数据区间的开始时间 | 2024-01-14 00:00:00 |
data_interval_end |
数据区间的结束时间 | 2024-01-15 00:00:00 |
execution_date |
等于 data_interval_start |
2024-01-14 00:00:00 |
ds |
execution_date 的日期字符串 |
2024-01-14 |
next_ds |
下一个调度日期 | 2024-01-15 |
def process_with_interval(**context):
# 推荐使用数据区间来确定处理范围(更语义化)
start = context["data_interval_start"] # pendulum.DateTime 对象
end = context["data_interval_end"]
print(f"处理数据区间:{start} 到 {end}")
# 查询这个时间区间的数据(左闭右开)
sql = f"""
SELECT *
FROM orders
WHERE create_time >= '{start}'
AND create_time < '{end}'
"""
在 SQL 模板中使用
query_task = SQLExecuteQueryOperator(
task_id="query_interval_data",
conn_id="mysql_prod",
sql="""
SELECT *
FROM orders
WHERE create_time >= '{{ data_interval_start }}'
AND create_time < '{{ data_interval_end }}'
""",
)
时区配置
Airflow 默认使用 UTC 时间。这对于国内数据工程师是个大坑,因为我们的业务数据通常是 UTC+8(北京时间)。
配置 Airflow 时区
# airflow.cfg 或环境变量
# [core]
# default_timezone = Asia/Shanghai
# 环境变量方式
# AIRFLOW__CORE__DEFAULT_TIMEZONE=Asia/Shanghai
或在 docker-compose.yaml 中:
environment:
AIRFLOW__CORE__DEFAULT_TIMEZONE: "Asia/Shanghai"
在 DAG 中指定时区
import pendulum
# 方式 1:在 DAG 中指定时区(推荐)
local_tz = pendulum.timezone("Asia/Shanghai")
with DAG(
dag_id="china_daily_etl",
schedule_interval="0 2 * * *",
start_date=datetime(2024, 1, 1, tzinfo=local_tz), # 带时区的 start_date
catchup=False,
) as dag:
pass
时区坑:@daily 在北京时间几点执行
配置 schedule_interval = "@daily"
等价于 "0 0 * * *"(每天 0 点)
Airflow 默认时区 = UTC
结果:若 DAG 未显式指定其他时区,区间在 UTC 0 点结束,即北京时间早上 8 点,届时最早具备调度条件;实际任务启动可能延后。
解决方案:
1. 用带 Asia/Shanghai 时区的 start_date 明确 DAG 时区:若仍用 @daily,则在北京时间零点结束区间;若需要北京时间凌晨 2 点,则使用 "0 2 * * *"。修改全局默认时区不会覆盖已有显式时区。
2. 或者让 DAG 明确使用 UTC 时区,再写 schedule_interval = "0 18 * * *"(UTC 18 点 = 北京时间次日 2 点)。
warning 时区是数据工程中最隐蔽的 bug 来源之一
建议团队统一以下规范:
- 所有 Airflow 实例统一使用
Asia/Shanghai时区 - 数据库中的时间字段注明时区(或统一 UTC 存储,查询时转换)
- DAG 文件的
start_date明确传入时区对象
SLA:任务超时监控
SLA(Service Level Agreement) 定义了任务必须在规定时间内完成,超时时触发告警。
from datetime import timedelta
# 方式 1:在 Task 级别设置 SLA
long_running_task = PythonOperator(
task_id="heavy_computation",
python_callable=run_computation,
# Airflow 2.x 传统 SLA:在本文的 cron 计划运行中,以该区间结束的计划调度时刻加 3 小时作为期限,而非实际启动后 3 小时
sla=timedelta(hours=3),
)
# 方式 2:在 DAG 级别设置 SLA Miss 回调
def sla_miss_callback(dag, task_list, blocking_task_list, slas, blocking_tis):
"""当任务超过 SLA 时触发此函数"""
print(f"SLA 超时警告!")
print(f"超时任务:{task_list}")
# 发送自定义告警(如钉钉或飞书)
send_dingtalk_alert(
f"Airflow SLA 告警:任务 {task_list} 超过 SLA 时间!"
)
with DAG(
dag_id="monitored_etl",
schedule_interval="0 2 * * *",
start_date=datetime(2024, 1, 1),
sla_miss_callback=sla_miss_callback,
# DAG 级别默认 SLA(可被 Task 级别覆盖)
default_args={
"sla": timedelta(hours=4),
},
) as dag:
pass
任务超时 vs SLA 的区别
| 参数 | 触发条件 | 触发后行为 |
|---|---|---|
execution_timeout |
单次任务执行超过指定时间 | 抛出超时异常;按重试配置进入重试或失败,不保证外部作业同步终止 |
sla |
Airflow 2.x 传统 SLA:本文 cron 计划运行中的任务未在区间结束的计划调度时刻加 SLA 时长之前成功完成 | 在 SLA 检查启用且满足触发条件时记录 SLA miss 并调用已配置的回调,不中止任务 |
# 同时设置两个:
task = PythonOperator(
task_id="important_task",
python_callable=run_task,
execution_timeout=timedelta(hours=2), # 单次执行超过 2 小时触发超时异常,后续按重试配置处理
sla=timedelta(hours=1), # 本文 cron 计划运行中,计划调度时刻后 1 小时仍未成功完成可产生 SLA miss
)
其他调度相关配置
depends_on_past:依赖上一次运行
default_args = {
"depends_on_past": True, # 通常要求上一逻辑运行的同名 Task 成功或跳过;首个实例及忽略历史依赖等情形需按版本和参数确认
}
使用场景:增量数据处理,约束同名任务实例按逻辑运行顺序执行;它不保证所有自然日都有运行,也不能代替数据完整性检查。
warning depends_on_past 的风险
如果某次任务失败且未恢复,后续依赖它的任务实例可能持续阻塞;可通过重试、清除后重跑等方式恢复,清除状态本身并不保证后续立即可运行。 在高频调度(如每小时)的 DAG 中谨慎使用。
max_active_runs:限制并发 DAG Run 数量
with DAG(
dag_id="safe_etl",
max_active_runs=1, # 同一时刻只允许 1 个 DAG Run 执行
# 防止补数据时大量 DAG Run 并发,压垮下游系统
) as dag:
pass
手动补数据
当需要重新处理历史数据时:
# 命令行补跑指定日期范围
airflow dags backfill \
--start-date 2024-01-01 \
--end-date 2024-01-31 \
my_etl_dag
# 清除指定日期的任务(重新运行)
airflow tasks clear my_etl_dag \
--start-date 2024-01-15 \
--end-date 2024-01-15
# 在 Web UI 中:
# 1. 进入 DAG 详情页
# 2. 切换到 Calendar/Grid View
# 3. 选择要重跑的日期
# 4. 点击 "Clear" 清除状态,Scheduler 会重新调度
小结
| 概念 | 关键点 |
|---|---|
execution_date |
逻辑日期,不是实际执行时间;本文 cron 数据区间模型中等于区间开始。@daily 区间在 1 月 15 日结束的计划运行通常有 ds = 1 月 14 日 |
data_interval_start/end |
推荐用来表示数据处理的时间范围,比 execution_date 更直观 |
| 时区 | 默认 UTC,国内建议配置 Asia/Shanghai;@daily 在 UTC 下是北京时间早上 8 点 |
| SLA | Airflow 2.x 传统 SLA 在满足检查条件时记录超期并触发已配置告警,不中止任务;execution_timeout 限制单次执行时长,触发超时异常后按重试配置处理 |
depends_on_past |
依赖上一次运行成功,慎用于高频 DAG |
下一篇:08-变量连接与配置管理 — 安全地管理数据源连接和配置参数
所属系列:00-Airflow学习路线图 | 上一篇:06-任务依赖与编排 | 下一篇:08-变量连接与配置管理
#Airflow #cron表达式 #execution_date #数据区间 #时区 #SLA #调度策略
info 前置知识
- 理解 DAG 的
start_date和schedule_interval基本概念,参考 第一个 DAG - 了解 cron 表达式基本格式(5 个字段的含义)
- 知道 UTC 时区和北京时间(Asia/Shanghai)的关系
掌握检查
- [ ] 能准确解释为什么在默认时区为 UTC 且 DAG 未显式指定其他时区时,
@daily计划运行最早在北京时间早上 8 点具备调度条件(UTC 00:00 = 北京 08:00),而实际启动可能延后 - [ ] 能写出"每天凌晨 2 点半"、"工作日每小时整点"、"每月 1 日和 15 日零点"这三个 cron 表达式
- [ ] 能解释
execution_date和data_interval_start的区别,以及为什么 Airflow 2.2+ 推荐用后者 - [ ] 知道 Airflow 2.x 传统 SLA miss 和
execution_timeout的行为区别:前者记录超期并在配置后告警,不中止任务;后者触发超时异常,按重试配置进入重试或失败 - [ ] 能说出
depends_on_past=True的危险场景:某次同名任务实例失败且未恢复时,后续依赖它的任务实例可能持续阻塞;首个实例及忽略历史依赖等例外需按版本和参数确认
实践练习
基础题:写一个 DAG,调度频率为"工作日(周一到周五)每天凌晨 1:30",时区设置为 Asia/Shanghai,start_date 为 2024-01-01,catchup=False。在 Task 中打印 {{ data_interval_start }} 和 {{ data_interval_end }},验证时间范围是否符合预期。
进阶题:解释这个场景:分别将美国东部标准时(EST,UTC-5)和北京时间(UTC+8)的凌晨 2 点换算为 UTC;再讨论同一个 DAG 使用固定调度时区时,两地同事应如何表示同一计划调度时刻。美国东部夏令时为 EDT(UTC-4),使用 America/New_York 时还须考虑切换日的不存在或重复本地时间。如果团队协作时需要统一沟通调度时间,应该以哪个时区为基准?