3. 任务编排概述
在自动驾驶数据闭环中,一条流水线往往要串联数据采集、脱敏、标注、质检、模型训练、仿真评测、回归验证等十几个环节。如果每个环节都靠人工触发脚本,不仅效率低,而且一旦某个环节失败,很难判断该从哪一步重跑。任务编排(Orchestration)正是为了解决这类问题:它把分散的任务组织成有向无环图(DAG),由工作流引擎按依赖关系自动调度、重试、监控和回溯。
一、编排的核心概念
编排的本质是“描述依赖 + 自动执行”。一个编排系统通常包含四个基本要素:
- 任务(Task):最小执行单元,例如“运行数据脱敏脚本”或“启动一次模型训练”。
- 依赖(Dependency):任务之间的先后约束,例如“标注完成”是“训练启动”的前置条件。
- 调度器(Scheduler):决定何时、以何种顺序、在哪个执行器上运行任务。
- 状态与元数据(State & Metadata):记录每个任务的运行状态、输入输出、耗时和日志,用于重试与审计。
与“脚本串联”相比,编排的关键差异在于:依赖是显式声明的,状态是持久化的,失败是可恢复的。这三点决定了数据闭环能否稳定地日复一日运行。
二、工作流引擎与 DAG 调度
工作流引擎负责解析 DAG、计算就绪任务、分配执行资源并回收状态。DAG 调度通常遵循以下流程:
- 解析 DAG 定义,构建任务节点与边的关系。
- 计算入度为 0 的节点,将其标记为“就绪”。
- 调度器将就绪任务放入执行队列,由执行器拉起容器或进程。
- 任务完成后更新状态,并递减下游节点的入度。
- 若某任务失败,根据重试策略决定重跑、跳过还是阻塞下游。
在自动驾驶场景中,DAG 往往不是一条直线,而是“多源输入、多路并行、单点汇聚”的结构。例如,同一批数据可能同时进入“标注质检”和“预标注”两条分支,最后汇聚到“训练集生成”。调度器需要支持并行分支、条件分支和动态任务映射(Dynamic Task Mapping),才能应对数据量波动。
关键点:DAG 调度不是简单的拓扑排序,而是“状态机 + 资源管理 + 失败恢复”的组合。调度器必须知道每个任务当前处于 queued、running、success、failed、upstream_failed 还是 skipped 状态。
三、依赖管理的三种粒度
依赖管理决定了任务之间的耦合程度。常见粒度包括:
| 依赖类型 | 说明 | 自动驾驶示例 |
|---|---|---|
| 任务级依赖 | 任务 A 完成后才执行任务 B | 数据脱敏完成后才能开始标注 |
| 数据级依赖 | 依赖某个数据集或分区就绪 | 某天采集的 rosbag 全部上传完成后触发解析 |
| 时间级依赖 | 按时间窗口或定时触发 | 每天凌晨 2 点启动昨日数据闭环流水线 |
依赖声明得越细,重跑粒度就越小,但编排复杂度也越高。实践中建议遵循“任务级为主、数据级为辅、时间级兜底”的原则,避免把所有依赖都写成时间触发,否则一旦上游延迟,下游就会读到不完整数据。
四、常见编排工具对比
目前主流的编排工具各有侧重,选择时需结合团队规模、部署环境和数据闭环的实时性要求。
| 工具 | 核心模型 | 适用场景 | 注意事项 |
|---|---|---|---|
| Apache Airflow | Python DAG | 批处理数据流水线、定时调度 | 调度延迟较高,不适合毫秒级任务 |
| Argo Workflows | Kubernetes CRD | 容器化任务、云原生环境 | 依赖 K8s,学习成本较高 |
| Prefect | Python 装饰器 | 动态 DAG、快速迭代 | 生态相对年轻,需评估稳定性 |
| Dagster | Asset-based | 数据资产血缘、强类型校验 | 概念较多,适合中大型团队 |
| Kubeflow Pipelines | K8s + ML Pipeline | 机器学习训练与推理流水线 | 偏 ML 场景,通用数据处理较弱 |
以 Airflow 为例,一个最小 DAG 定义如下:
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime
with DAG(
dag_id="auto_driving_data_loop",
start_date=datetime(2024, 1, 1),
schedule_interval="0 2 * * *",
catchup=False,
) as dag:
ingest = BashOperator(task_id="ingest", bash_command="python ingest.py")
label = BashOperator(task_id="label", bash_command="python label.py")
train = BashOperator(task_id="train", bash_command="python train.py")
ingest >> label >> train
这段代码声明了“采集 → 标注 → 训练”的线性依赖,并由 Airflow 每天凌晨 2 点自动触发。若训练失败,Airflow 会保留上游成功状态,只需重跑 train 任务即可,无需从头再来。
五、案例:数据闭环中的编排落地
某自动驾驶团队将每日闭环拆分为 6 个阶段:数据上传校验、脱敏、自动标注、人工抽检、训练集生成、模型训练与评测。他们使用 Argo Workflows 在 K8s 上运行,每个阶段是一个容器化任务,依赖关系通过 DAG 声明。关键设计包括:
- 数据上传校验使用数据级依赖,只有当天所有 rosbag 到齐才触发下游。
- 自动标注与人工抽检并行执行,抽检结果通过条件分支决定是否回退重标。
- 训练任务设置重试 2 次,失败后自动发送告警并保留中间产物。
- 整个 DAG 的元数据写入数据湖,供后续血缘分析和问题追溯。
经验:编排工具不是越重越好。小团队可以从 Airflow 或 Prefect 起步,先把依赖显式化;当任务全面容器化、需要弹性伸缩时,再迁移到 Argo Workflows 或 Kubeflow。
六、常见误区与复盘
在实际落地中,以下误区最容易导致编排系统“看起来能用,实际上难维护”:
- 把 DAG 写成大泥球:所有任务挤在一个 DAG 里,任一环节改动都要全量回归。应按数据域或阶段拆分子 DAG。
- 依赖声明不完整:只写任务依赖,忽略数据分区就绪,导致下游读到空数据。
- 重试策略一刀切:对所有任务都重试 3 次,可能让数据错误被重复放大。应区分“可重试”和“需人工介入”。
- 缺少状态可观测性:没有统一看板和告警,失败任务靠人工发现,闭环时效无法保证。
复盘时建议重点检查三个指标:任务平均排队时长、失败重跑率、端到端闭环耗时。它们能直接反映编排策略是否合理。只有把依赖、状态和重试都管理清楚,任务编排才能真正支撑自动驾驶数据闭环的规模化运转。