调度系统选型:Airflow vs Dagster vs Prefect

选调度系统,说白了就是选你未来两年的「队友」。队友选得好,每天准点下班;选得不好,半夜爬起来救火。我这些年三个系统都用过,踩过不少坑,今天把真实感受摊开来讲。

先看一张全景图

下面这张图,是我自己梳理的选型决策路径。你跟着它走,基本不会跑偏。

调度系统选型决策框架 你的团队规模 & 技术栈 小团队(1-5人) 大团队(5人+) Prefect(快速上手) Dagster(中等规模) Airflow(生态最成熟) Python原生 · 云原生 资产感知 · 强类型 生态成熟 · 社区大 💡 选型核心原则 • 团队小 → 优先开发体验(Prefect / Dagster) • 团队大 → 优先生态和运维(Airflow) • 没有银弹,只有最适合你当前阶段的工具

三个选手的基本画像

先别急着看对比表,我先把三个工具的核心气质说清楚。

Airflow:老大哥,生态最全

Airflow 是调度界的「Linux」。2015 年 Airbnb 开源,现在已经是 Apache 顶级项目。我最早接触它是在 2018 年,那时候 Dagster 和 Prefect 还没出生。Airflow 最大的优势是社区和生态——你遇到过的坑,99% 都有人踩过,Stack Overflow 上一搜就有答案。

但它的缺点也很明显:DAG 定义是「静态」的,调试起来比较痛苦。我记得有一次生产环境上一个 DAG 死活不触发,排查了半天,结果是 scheduler 的时区配置和 worker 不一致。这种问题在 Airflow 里太常见了。

Dagster:资产感知,强类型

Dagster 是后来者,2019 年才出来。它最大的创新是「资产感知」——你不再只关心 DAG 跑没跑完,而是关心「这张表有没有更新」、「这个模型有没有产出」。我个人特别喜欢它的类型系统,每个输入输出都可以声明类型,跑之前就能发现类型不匹配的问题。

我在一个金融项目中用过 Dagster,团队里有个新人写错了一个字段类型,Dagster 在编译阶段就报错了,而不是等到凌晨 3 点跑崩了才发现。嗯,这种体验在 Airflow 里是享受不到的。

Prefect:云原生,开发体验好

Prefect 的口号是「Pythonic workflow」。它的 API 设计非常优雅,用装饰器就能定义 DAG,而且支持自动重试、缓存、并发控制。Prefect 2.0 之后更是把开发体验拉满了——你甚至可以在 Jupyter Notebook 里调试 DAG。

不过 Prefect 的社区相对小一些,有些高级功能需要上 Prefect Cloud(付费)。我有个朋友在小公司用 Prefect,后来业务量大了,发现自建 Prefect Server 的稳定性不如 Airflow,又迁回去了。

核心对比表

下面这张表,我建议你收藏。选型的时候拿出来对照一下,基本能筛掉 80% 的选项。

对比维度 Airflow Dagster Prefect
开源协议 Apache 2.0 Apache 2.0 Apache 2.0(核心)
DAG 定义方式 Python 脚本(静态) Python + 装饰器(动态) Python + 装饰器(动态)
调度精度 分钟级 秒级 秒级
依赖管理 Task 级别 Asset 级别(强类型) Task 级别
UI 体验 功能全,但老旧 现代化,DAG 可视化好 现代化,交互流畅
重试机制 内置,配置灵活 内置,支持策略 内置,自动重试
缓存 需插件 内置(基于输入哈希) 内置(自动缓存)
云原生支持 K8s Executor K8s 原生 Prefect Cloud / Server
社区规模 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
学习曲线 中等(运维复杂) 中等(概念多) 低(Pythonic)
适合场景 大型团队、复杂依赖 数据资产密集型 中小团队、快速迭代

避坑指南:我踩过的三个坑

⚠️ 坑一:Airflow 的时区问题

我曾经有一个 DAG 设置每天 8:00 执行,结果连续三天都在 7:00 跑了。排查了半天,发现是 scheduler 的时区是 UTC,而 DAG 配置里写的是 Asia/Shanghai。Airflow 默认所有时间都是 UTC,你必须在 airflow.cfg 里设置 default_timezone = Asia/Shanghai,并且每个 DAG 也要显式指定 timezone 参数。这个坑,我建议你一开始就规避。

⚠️ 坑二:Dagster 的「过度设计」

Dagster 的概念确实多:Asset、Op、Graph、Job、Sensor、Schedule……我刚开始用的时候,光理解这些概念就花了一周。后来我发现,其实 80% 的场景只需要 Asset 和 Op 就够了。不要一开始就想着把所有概念都用上,否则你的 DAG 会变得非常复杂。记住:简单才是王道。

⚠️ 坑三:Prefect 的「黑盒」问题

Prefect 的自动重试和缓存确实好用,但有时候你根本不知道它为什么重试、为什么缓存命中。我遇到过一个问题:一个 Task 明明失败了,但 Prefect 自动重试了 3 次都失败,最后它把失败结果缓存了,导致后续所有依赖它的 Task 都拿到了错误数据。排查了 2 个小时才发现是缓存策略配置错了。所以,用 Prefect 一定要把日志级别调到 DEBUG,并且定期检查缓存状态。

选型决策树

如果你还在纠结,我建议你按这个顺序问自己三个问题:

  1. 团队有多少人? 1-5 人 → 优先 Prefect 或 Dagster;5 人以上 → 优先 Airflow。
  2. 你们最关心什么? 开发体验 → Prefect;数据资产 → Dagster;生态稳定 → Airflow。
  3. 未来会不会上 K8s? 会 → 三个都支持,但 Dagster 和 Prefect 更原生;不会 → Airflow 更省心。

💡 我的个人建议

如果你是新项目,我建议从 Prefect 或 Dagster 开始。Airflow 虽然成熟,但它的运维成本确实高。我见过太多团队把 Airflow 搭起来之后,花在维护上的时间比写 DAG 还多。当然,如果你团队里已经有 Airflow 专家,那另当别论。

代码示例:同一个 DAG,三种写法

光说不练假把式。下面我用一个简单的 ETL 场景——从 API 拉数据、清洗、入库——分别用三个工具实现。你感受一下风格差异。

Airflow 版本

from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime

def extract():
    # 从 API 拉数据
    pass

def transform():
    # 清洗数据
    pass

def load():
    # 写入数据库
    pass

with DAG(
    dag_id='simple_etl',
    start_date=datetime(2024, 1, 1),
    schedule='@daily',
    catchup=False
) as dag:
    extract_task = PythonOperator(task_id='extract', python_callable=extract)
    transform_task = PythonOperator(task_id='transform', python_callable=transform)
    load_task = PythonOperator(task_id='load', python_callable=load)

    extract_task >> transform_task >> load_task

Dagster 版本

from dagster import asset, define_asset_job, Definitions

@asset
def raw_data():
    # 从 API 拉数据
    return [...]

@asset
def cleaned_data(raw_data):
    # 清洗数据
    return [...]

@asset
def stored_data(cleaned_data):
    # 写入数据库
    pass

etl_job = define_asset_job(name='etl_job', selection=[raw_data, cleaned_data, stored_data])

defs = Definitions(assets=[raw_data, cleaned_data, stored_data], jobs=[etl_job])

Prefect 版本

from prefect import flow, task

@task
def extract():
    # 从 API 拉数据
    return [...]

@task
def transform(data):
    # 清洗数据
    return [...]

@task
def load(data):
    # 写入数据库
    pass

@flow
def etl_flow():
    raw = extract()
    cleaned = transform(raw)
    load(cleaned)

if __name__ == '__main__':
    etl_flow.serve(name='daily_etl', cron='0 8 * * *')

你看,Prefect 的代码最简洁,Dagster 的资产概念最清晰,Airflow 最传统。选哪个,取决于你的审美和团队习惯。

最后说两句

调度系统选型没有标准答案。我见过用 Airflow 用得飞起的 3 人小团队,也见过用 Prefect 翻车的大厂。关键是你得清楚自己的场景:团队规模、技术栈、运维能力、未来规划。

如果你现在还在犹豫,我建议你三个都搭一个 demo 跑跑。花一天时间,把同一个 DAG 用三个工具实现一遍,哪个顺手就用哪个。毕竟,工具是为人服务的,别为了工具而工具。