crontab 管不住时,该上 Airflow 了

凌晨三点脚本挂了,你第二天中午才知道。

摘要

Airflow 把一堆定时脚本变成一张有依赖、能重试、可回看的任务图。本文用 Airflow 3 从安装到跑通两条联动的管道(上游产出数据、下游自动触发),顺带列出老教程在 3.x 里会踩的坑。

crontab 的问题,不在"定时"

一开始,一条 crontab 就够了:每天两点拉数据,三点算报表。

脚本多到十几个以后,麻烦就来了:

  • 依赖靠猜时间。 拉数据那天慢了半小时,三点的报表照样开跑,算的是昨天的数。
  • 失败没人知道。 日志散在各台机器上,没有统一的地方看"今天哪些成功了"。
  • 重跑全靠手。 补跑上周三的数据,得登机器、改参数、按顺序一条条敲。

这些都不是"定时"的问题,是"编排"的问题。Airflow 就是为这个生的:2014 年 Airbnb 的数据工程师 Maxime Beauchemin 发起了它,2019 年成为 Apache 顶级项目,今天在 GitHub 上有 4.7 万多颗星。

Airflow 是什么

一句话:用 Python 代码描述任务和依赖,由调度器按时间或事件去跑。

几个核心概念:

概念 意思
DAG 有向无环图,一条完整的管道,比如"拉数据 → 清洗 → 出报表"
Task DAG 里的一个节点,一件具体要做的事
Scheduler 调度器,决定什么时候该跑哪个任务
Asset Airflow 3 的叫法(2.x 叫 Dataset),表示一份数据,可以拿来触发下游

DAG 只是一个放在 dags/ 目录里的 .py 文件,能写进 Git、能做代码评审,这是它和图形化拖拽调度工具最大的不同。

核心优势

  • 依赖是显式的。 上游没成功,下游就不跑,不再靠"错开一小时"来赌。
  • 失败自动重试,状态一目了然。 每个任务每次运行的状态和日志都在网页上,红绿一眼看清。
  • 补跑和重跑是一等公民。 选中某天某个任务点"清除",它会带着当时的日期参数重新跑。
  • 生态大。 数据库、云存储、Spark、dbt、Kubernetes 都有现成的 provider 包,不用自己写连接代码。
  • 可以按数据触发。 可以说"这张表更新了就触发下游",不必再按钟点硬排(2.x 叫 Dataset,Airflow 3 改名 Asset 并继续加强)。

面向人群

  • 手上有 5 个以上相互依赖的定时脚本、开始为"谁先谁后"头疼的人;
  • 做 ETL、数据仓库、报表的数据工程师和分析师;
  • 需要把模型训练、批量推理按流程串起来的算法工程师。

不太适合:毫秒级的实时流处理(那是 Flink、Kafka 的活),或者只有一两个互不相干的定时任务------这时候 crontab 依然是最省事的选择。

实践:从安装到两条管道联动

下面在本机跑通。需要 Python 3.10--3.14,Windows 请在 WSL2 里操作。

第 1 步:安装

Airflow 依赖很多,必须带官方的 constraint 文件安装,否则很容易装出一套互相冲突的版本:

bash 复制代码
uv venv --python 3.12 && source .venv/bin/activate

AIRFLOW_VERSION=3.3.2
PYTHON_VERSION="$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
CONSTRAINT_URL="https://raw.githubusercontent.com/apache/airflow/constraints-${AIRFLOW_VERSION}/constraints-${PYTHON_VERSION}.txt"

uv pip install "apache-airflow==${AIRFLOW_VERSION}" --constraint "${CONSTRAINT_URL}"

官方说明里写得很清楚:Poetry 和 pip-tools 不受支持,用 pip 或 uv。

第 2 步:一条命令启动

bash 复制代码
airflow standalone

它会初始化数据库、创建管理员、启动全部组件。打开浏览器访问 localhost:8080,用户名 admin,自动生成的密码不一定会打在终端上,直接看这个文件:

bash 复制代码
cat ~/airflow/simple_auth_manager_passwords.json.generated

standalone 只适合本地学习,生产环境要把各组件分开部署。

第 3 步:写第一个 DAG

在 ~/airflow/dags/ 下新建 daily_orders.py:

python 复制代码
from datetime import timedelta

import pendulum

from airflow.sdk import Asset, dag, task

orders_report = Asset("file:///tmp/orders_report.json")


@dag(
    schedule="0 2 * * *",  # 每天凌晨两点
    start_date=pendulum.datetime(2026, 10, 1, tz="Asia/Shanghai"),
    catchup=False,
    default_args={"retries": 2, "retry_delay": timedelta(minutes=5)},
    tags=["demo"],
)
def daily_orders():
    @task
    def extract() -> dict:
        # 实际项目里这里是查数据库或调接口
        return {"1001": 301.27, "1002": 433.21, "1003": 502.22}

    @task
    def transform(orders: dict) -> float:
        return round(sum(orders.values()), 2)

    @task(outlets=[orders_report])
    def load(total: float):
        import json

        with open("/tmp/orders_report.json", "w") as f:
            json.dump({"total": total}, f)
        print(f"今日订单总额:{total}")

    load(transform(extract()))


daily_orders()

几个要点:

  • @task 函数的返回值会自动传给下一个任务,load(transform(extract())) 这一行同时定义了数据流和依赖顺序。
  • retries=2 表示任务失败会自动再试两次,间隔 5 分钟。
  • outlets=[orders_report] 告诉 Airflow:"这个任务成功后,orders_report 这份数据更新了。"

第 4 步:不开调度器,先在命令行测一遍

bash 复制代码
airflow dags test daily_orders

整条 DAG 会在当前进程里跑一遍,日志直接打到终端。写 DAG 时先用它调通,比在网页上反复点快得多。

第 5 步:让数据更新来触发下游

再建一个 notify.py,不写时间,而是"等报表更新":

python 复制代码
import pendulum

from airflow.sdk import Asset, dag, task

orders_report = Asset("file:///tmp/orders_report.json")


@dag(schedule=[orders_report], start_date=pendulum.datetime(2026, 10, 1), catchup=False)
def notify_on_report():
    @task
    def send():
        print("报表已更新,发通知")

    send()


notify_on_report()

daily_orders 里的 load 一成功,notify_on_report 就会被触发。如果 schedule 里列了多个资产,Airflow 会等它们都更新过才跑。

回到网页,把两个 DAG 都打开(新 DAG 默认是暂停状态),手动触发一次 daily_orders,就能看到下游自己跟着跑起来。

老教程在 Airflow 3 里会踩的坑

网上大量教程还是 2.x 写法,照抄轻则弹弃用警告,重则直接报错:

2.x 写法 Airflow 3
schedule_interval="@daily" 参数已移除,统一用 schedule
from airflow.decorators import dag, task 改从 airflow.sdk 导入,旧路径已弃用
不写 catchup 默认补跑历史 默认不补跑,需要补跑要显式写 catchup=True
任务里用 execution_date 已移除,改用 logical_date
SubDAG 已移除,用 TaskGroup 或资产代替
任务里直接连元数据库 不再允许,走官方提供的接口

存量代码可以先跑一遍 ruff:AIR301、AIR302 挑出 Airflow 3 里会直接报错的写法,AIR311、AIR312 挑出"还能跑但建议改"的写法(比如 airflow.decorators 旧导入)。

排错速查

  • 新 DAG 在网页上看不到: 先跑 airflow dags list-import-errors,多半是文件里有语法错误或导入失败;没有报错就再等一会儿,目录默认每 5 分钟扫描一次。
  • DAG 出现了但不跑: 检查是不是还处于暂停状态,以及 start_date 是否在未来。
  • 装完一启动就报依赖错误: 基本是没带 constraint 文件,删掉虚拟环境按第 1 步重装。
  • 登录密码找不到: 看 $AIRFLOW_HOME/simple_auth_manager_passwords.json.generated。

收藏这三行

  1. 装:带 constraint 文件装,本地用 airflow standalone 起。
  2. 写:airflow.sdk 里的 @dag + @task,返回值即依赖,失败靠 retries 兜底。
  3. 测:先 airflow dags test 调通,再在网页上打开 DAG 交给调度器;下游用 schedule=[Asset(...)] 跟着数据走。

项目地址:github.com/apache/airflow

相关推荐
斯维赤1 小时前
微信步数的10点定时推送,看似简单,实则暗藏玄机
java·后端
系统集成-鹰翔1 小时前
ERP接口月度统计少了零单月份:SQLite分组与最近三个月均值排查
javascript·数据库·经验分享·后端
我的xiaodoujiao1 小时前
Django 权限系统项目实战详细图文教程 3--系统登录功能实现
后端·python·django
SimonKing1 小时前
号外号外,Stream-Nexus推送项目支持集群部署了
java·后端·程序员
老王IT1 小时前
Rust开发环境搭建教程
开发语言·后端·rust
武子康1 小时前
HunyuanVideo-1.5 跑得更快,先减步数还是开缓存?
人工智能·后端·agent
chenbingjie_c2 小时前
C++ 模板进阶:从 “会用“ 到 “懂底层“(非类型参数・特化・分离式编译)
c++·后端
yunyi2 小时前
给桌面客户端做下载加速:从「桶直接公开」到「边缘优先、源站兜底」的三代演进
前端·后端
学长毕设2 小时前
基于SpringBoot的社区体育馆预约系统的设计与实现
java·spring boot·后端