3. 任务编排概述

在自动驾驶数据闭环中,一条流水线往往要串联数据采集、脱敏、标注、质检、模型训练、仿真评测、回归验证等十几个环节。如果每个环节都靠人工触发脚本,不仅效率低,而且一旦某个环节失败,很难判断该从哪一步重跑。任务编排(Orchestration)正是为了解决这类问题:它把分散的任务组织成有向无环图(DAG),由工作流引擎按依赖关系自动调度、重试、监控和回溯。

一、编排的核心概念

编排的本质是“描述依赖 + 自动执行”。一个编排系统通常包含四个基本要素:

  • 任务(Task):最小执行单元,例如“运行数据脱敏脚本”或“启动一次模型训练”。
  • 依赖(Dependency):任务之间的先后约束,例如“标注完成”是“训练启动”的前置条件。
  • 调度器(Scheduler):决定何时、以何种顺序、在哪个执行器上运行任务。
  • 状态与元数据(State & Metadata):记录每个任务的运行状态、输入输出、耗时和日志,用于重试与审计。

与“脚本串联”相比,编排的关键差异在于:依赖是显式声明的,状态是持久化的,失败是可恢复的。这三点决定了数据闭环能否稳定地日复一日运行。

二、工作流引擎与 DAG 调度

工作流引擎负责解析 DAG、计算就绪任务、分配执行资源并回收状态。DAG 调度通常遵循以下流程:

  1. 解析 DAG 定义,构建任务节点与边的关系。
  2. 计算入度为 0 的节点,将其标记为“就绪”。
  3. 调度器将就绪任务放入执行队列,由执行器拉起容器或进程。
  4. 任务完成后更新状态,并递减下游节点的入度。
  5. 若某任务失败,根据重试策略决定重跑、跳过还是阻塞下游。

在自动驾驶场景中,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 次,可能让数据错误被重复放大。应区分“可重试”和“需人工介入”。
  • 缺少状态可观测性:没有统一看板和告警,失败任务靠人工发现,闭环时效无法保证。

复盘时建议重点检查三个指标:任务平均排队时长、失败重跑率、端到端闭环耗时。它们能直接反映编排策略是否合理。只有把依赖、状态和重试都管理清楚,任务编排才能真正支撑自动驾驶数据闭环的规模化运转。

自动驾驶数据闭环任务编排 DAG 依赖显式声明 · 状态持久化 · 失败可恢复 数据采集 rosbag / 传感器 上传校验 数据级依赖 脱敏处理 任务级依赖 自动标注 并行分支 A 人工抽检 并行分支 B 训练集生成 汇聚节点 模型训练 重试 2 次 仿真评测 回归验证 抽检不通过→回退 任务状态: queued running success failed upstream_failed skipped 调度器按入度递减推进,失败任务按策略重试或阻塞下游,元数据写入数据湖供血缘追溯。