04-任务编排与DAG

任务编排与 DAG

前面几篇讲的,基本还是“一个任务,一条线”这类模型。但真实业务很快就会遇到更复杂的情况:

  • 有些步骤必须串行
  • 有些步骤可以并行
  • 有些步骤依赖多个前置结果
  • 某一步失败后,只想重跑它和它后面的分支

这时候再把流程硬编码成一大串 if/else 或顺序函数,就会越来越难维护。

这就是任务编排存在的意义。

什么是 DAG

DAGDirected Acyclic Graph,中文叫 有向无环图

拆开理解:

  • 有向:节点之间有依赖方向
  • 无环:不能出现 A 依赖 B,B 又绕回依赖 A

在任务系统里,可以把它理解成:

一个任务不是单步执行,而是由多个节点按照依赖关系组织成的一张图。

一个直观例子

还是用“合同解析任务”举例。用户上传合同后,我们希望执行:

  1. 预处理文件
  2. OCR 识别文本
  3. 图片语义分析
  4. 提取结构化字段
  5. 做规则校验
  6. 建索引并入库
  7. 通知用户

其中:

  • OCR图片语义分析 可以并行
  • 提取结构化字段 必须等前面两个都完成
  • 通知用户 必须等最终入库完成

这就非常适合用 DAG 表达:

flowchart TD
		A[上传文件] --> B[预处理]
		B --> C[OCR]
		B --> D[图片语义分析]
		C --> E[字段提取]
		D --> E
		E --> F[规则校验]
		F --> G[建索引并入库]
		G --> H[通知用户]

这张图比“写一大段代码说明流程”更直观,因为它直接展示了:

  • 谁依赖谁
  • 哪些节点能并行
  • 最终在哪个节点汇合

为什么任务编排不能只靠代码硬写

当然可以把上面的流程直接写成程序,但一旦业务变化,问题就来了。

例如今天产品说:

  • OCR 前面要加一个“文件安全检查”节点
  • 通知前还要多加一个“生成摘要”节点
  • 某个租户不需要图片语义分析

如果你把流程写死在代码里,每次都要改逻辑、改依赖、改条件分支,最后会越来越难维护。

而 DAG 的价值就在于:

  1. 结构显式化:依赖关系一眼就能看懂
  2. 天然支持并行:没有依赖的节点可以同时跑
  3. 支持局部重跑:失败时不一定要从头开始
  4. 易于可视化:产品、研发、运维都看得懂
  5. 适合配置化:流程可以做成 DSL 或数据库配置

编排器到底做什么

有了 DAG 以后,还需要一个“编排器”来驱动整个图运行。

编排器通常要做这些事:

  1. 找出当前所有可执行节点
  2. 把可执行节点派发给 Worker
  3. 等节点完成后更新状态
  4. 解锁下游节点
  5. 判断整个工作流是否结束

可以用下面这个简化流程理解:

flowchart LR
		A[创建工作流实例] --> B[找出无依赖节点]
		B --> C[派发给 Worker]
		C --> D[节点完成并回写状态]
		D --> E{是否解锁新节点}
		E -- 是 --> C
		E -- 否 --> F{是否全部完成}
		F -- 否 --> C
		F -- 是 --> G[工作流结束]

所以编排器本质上不是“做业务”,而是“调度节点之间的关系”。

一个节点应该具备哪些信息

把 DAG 真正落地时,每个节点一般至少要有这些元信息:

字段 作用
nodeId 节点标识
type 节点类型,如 ocrextract
dependsOn 依赖哪些前置节点
timeout 超时时间
retryPolicy 重试策略
input 输入参数来源
output 输出结果位置

例如一个极简 DSL 可以长这样:

nodes:
	- id: preprocess
		type: preprocess
		dependsOn: []

	- id: ocr
		type: ocr
		dependsOn: [preprocess]

	- id: image_analysis
		type: image_analysis
		dependsOn: [preprocess]

	- id: extract_fields
		type: extract_fields
		dependsOn: [ocr, image_analysis]

这时候流程就从“硬编码逻辑”变成了“数据驱动逻辑”。

简单案例:AI 内容生成工作流

以 AI 报告生成为例,也很适合 DAG。

任务可以拆成:

  1. 拉取原始数据
  2. 并行生成摘要、图表、关键词
  3. 合并结果生成完整报告
  4. 导出 PDF
  5. 通知用户下载

这里“摘要、图表、关键词”三步就是天然并行节点。

如果用 DAG,你就能做到:

  • 提升整体耗时表现
  • 某个分支失败时只重跑该分支
  • 更容易把每一步耗时单独统计出来

DAG 和消息队列是什么关系

很多人会把这两个概念混在一起,其实它们分工不同:

  • 消息队列 负责传递任务与节点执行消息
  • DAG 编排器 负责决定“下一个该跑谁”

可以简单理解为:

  • 队列是运输系统
  • DAG 是交通规则与调度图

没有 DAG,你只能做简单任务流;没有队列,大规模节点执行又不稳定。

什么时候值得上 DAG

下面这些情况,通常说明你已经值得上编排了:

  • 任务步骤超过 3 到 5 个
  • 某些步骤可以并行
  • 不同任务流只是在节点组合上不同
  • 经常需要“从某一步继续跑”
  • 希望把流程可视化给研发或运营看

如果只是一个很短的线性流程,直接顺序执行未必有必要引入完整编排系统。

初学者最容易踩的坑

1. 节点切得过细

每一步都拆成节点,会让编排成本和状态复杂度飙升。

2. 节点切得过粗

一整个大流程只有一个节点,那又失去了并行和重跑的价值。

3. 没有明确定义节点输入输出

最后节点之间靠共享全局变量或临时表通信,会非常难维护。

4. 忘了“无环”

一旦出现循环依赖,编排器就没法判断执行顺序。

这一篇要记住的核心点

  1. DAG 适合表达多步骤、有依赖、可并行的任务流
  2. 编排器负责调度依赖关系,队列负责运输执行消息
  3. DAG 的最大价值是:并行、重跑、可视化、配置化

下一篇继续补上另一个关键问题:这些任务和节点的状态,应该存在哪里,前端又该怎么把进度展示出来。

github