2. 任务调度系统概述:批处理与流处理的区别、调度引擎的演进历史
在数据工程领域,任务调度系统是数据管线的“中枢神经系统”。它负责按照预定义的依赖关系、时间策略或事件触发条件,协调各个数据处理任务的执行顺序、资源分配与失败重试。理解批处理与流处理的本质差异,以及调度引擎从简单定时器到现代DAG编排器的演进脉络,是掌握分布式数据架构的基础。
2.1 批处理与流处理:两种数据处理范式
批处理(Batch Processing)与流处理(Stream Processing)代表了数据处理的两种极端哲学。批处理假设数据是“有界的”(Bounded),即数据在开始处理前已经完整到达;流处理则假设数据是“无界的”(Unbounded),数据持续不断地产生,需要实时或近实时地处理。
| 对比维度 | 批处理 (Batch) | 流处理 (Stream) |
|---|---|---|
| 数据特性 | 有界、静态、已知大小 | 无界、动态、持续到达 |
| 延迟要求 | 分钟级 ~ 小时级(高延迟) | 毫秒级 ~ 秒级(低延迟) |
| 触发方式 | 时间调度(Cron)、数据就绪 | 事件到达、水位线(Watermark) |
| 处理语义 | Exactly-Once(精确一次) | At-Least-Once / Exactly-Once |
| 典型引擎 | MapReduce, Spark Batch, Hive | Flink, Kafka Streams, Spark Streaming |
| 调度模型 | DAG 任务链,依赖上游完成 | 拓扑算子,依赖上游数据流 |
2.2 调度引擎的演进历史
从简单的 Unix Cron 到现代的 Dagster,调度引擎的演进反映了数据工程对依赖管理、可观测性、数据血缘需求的逐步深化。下图展示了四个关键阶段的架构特征。
2.3 各阶段深度解析
2.3.1 Cron:一切的开端
Cron 是 Unix/Linux 系统内置的定时任务工具,通过 crontab 文件定义任务执行时间。它简单、可靠,但存在致命缺陷:无依赖管理、无失败重试、无状态持久化。在复杂数据管线中,Cron 脚本往往需要自行实现锁机制、日志轮转和依赖检查,导致代码与业务逻辑高度耦合。
# 典型的 Cron 表达式:每天凌晨 2 点执行数据同步
0 2 * * * /usr/local/bin/sync_data.sh
# 问题:如果 sync_data.sh 依赖前一天的 ETL 完成,Cron 无法感知
# 只能通过脚本内部轮询或固定等待时间来解决
2.3.2 Oozie:Hadoop 时代的 DAG 先驱
Apache Oozie 是 Hadoop 生态下的工作流调度系统,首次引入了有向无环图(DAG)的概念。用户通过 XML 文件定义任务节点(Action)和控制节点(Fork/Join/Kill),支持时间触发和数据触发。Oozie 解决了 Cron 的依赖缺失问题,但 XML 配置冗长、调试困难,且与 Hadoop 生态强绑定。
<!-- Oozie 工作流 XML 示例(简化) -->
<workflow-app name="data_pipeline" xmlns="uri:oozie:workflow:0.5">
<start to="extract"/>
<action name="extract">
<map-reduce>
<job-tracker>${jobTracker}</job-tracker>
<name-node>${nameNode}</name-node>
<configuration>
<property>
<name>mapred.mapper.class</name>
<value>ExtractMapper</value>
</property>
</configuration>
</map-reduce>
<ok to="transform"/>
<error to="fail"/>
</action>
<kill name="fail">
<message>Extract failed: ${wf:errorMessage(wf:lastErrorNode())}</message>
</kill>
<end name="end"/>
</workflow-app>
2.3.3 Airflow:Python 原生 DAG 的普及者
Apache Airflow 由 Airbnb 于 2014 年开源,彻底改变了任务调度的开发体验。它使用 Python 代码定义 DAG,将调度器(Scheduler)、执行器(Executor)、元数据库(Metadata DB)和 Web UI 分离为独立组件。Airflow 的核心创新包括:
- Pythonic DAG 定义:开发者可以用 Python 函数、循环、上下文管理器动态生成任务图。
- 丰富的 Operator 生态:内置 BashOperator、PythonOperator、PostgresOperator 等,社区贡献了数百个 Provider。
- 回填(Backfill):支持对历史数据区间重新执行 DAG,是数据修复的关键能力。
- 调度器高可用:通过 Celery 或 Kubernetes Executor 实现分布式执行。
# Airflow DAG 示例:简单的 ETL 管线
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta
default_args = {
'owner': 'data_team',
'depends_on_past': False,
'retries': 2,
'retry_delay': timedelta(minutes=5),
}
with DAG(
dag_id='simple_etl',
start_date=datetime(2024, 1, 1),
schedule_interval='0 3 * * *', # 每天凌晨3点
catchup=False,
default_args=default_args,
) as dag:
def extract(**context):
# 模拟数据抽取
return {"status": "success", "records": 10000}
def transform(**context):
ti = context['ti']
extract_result = ti.xcom_pull(task_ids='extract')
# 模拟数据转换
return {"transformed": extract_result["records"] * 2}
extract_task = PythonOperator(
task_id='extract',
python_callable=extract,
)
transform_task = PythonOperator(
task_id='transform',
python_callable=transform,
)
extract_task >> transform_task # 设置依赖
2.3.4 Dagster:数据资产编排的新范式
Dagster 由 Elementl 公司于 2019 年开源,定位为“数据编排器”(Data Orchestrator),而非单纯的任务调度器。它引入了资产(Asset)作为一等公民,将关注点从“任务执行顺序”转移到“数据资产的生产与消费关系”。核心特性包括:
- 软件定义资产(Software-defined Assets):每个 Python 函数明确声明其产出和依赖的资产,DAG 由资产依赖自动推导。
- 类型系统与验证:在 DAG 编译阶段即可检查输入输出类型是否匹配,避免运行时错误。
- 分区与物化管理:原生支持时间分区、维度分区,以及资产物化(Materialization)的版本控制。
- 可观测性优先:Dagit UI 提供资产谱系图、事件日志、资源使用分析,支持一键回溯。
# Dagster 资产定义示例:数据管线以资产为中心
from dagster import asset, Output, AssetIn, Definitions
import pandas as pd
@asset
def raw_sales() -> pd.DataFrame:
"""原始销售数据资产"""
# 模拟从 API 拉取数据
return pd.DataFrame({"date": ["2024-01-01"], "amount": [100.0]})
@asset(ins={"raw_sales": AssetIn()})
def cleaned_sales(raw_sales: pd.DataFrame) -> pd.DataFrame:
"""清洗后的销售数据资产"""
return raw_sales.dropna()
@asset(ins={"cleaned_sales": AssetIn()})
def daily_summary(cleaned_sales: pd.DataFrame) -> Output[pd.DataFrame]:
"""每日销售汇总资产"""
summary = cleaned_sales.groupby("date").sum()
return Output(
value=summary,
metadata={"row_count": len(summary)}
)
# 定义数据平台
defs = Definitions(assets=[raw_sales, cleaned_sales, daily_summary])
2.4 批处理与流处理在调度系统中的融合
现代调度系统(如 Dagster、Airflow 2.x + 传感器)开始尝试统一批处理与流处理的调度模型。核心思路是将流处理任务视为“长期运行的传感器”,将批处理任务视为“一次性执行单元”,通过事件驱动机制连接两者。
这种融合架构的关键在于事件驱动:流处理任务在处理完一个微批次或达到某个水位线后,向事件总线发送“数据就绪”信号;批处理调度器监听这些信号,触发对应的 DAG 执行。Dagster 的 @sensor 装饰器和 Airflow 的 FileSensor / ExternalTaskSensor 都是这种模式的典型实现。