2. 任务编排核心概念:DAG、串并行编排、扇入扇出与状态机模型
在异步任务编排系统中,核心挑战在于如何描述任务之间的依赖关系、执行顺序以及运行时状态转换。本章将系统讲解构成编排引擎的四大基石:DAG(有向无环图)、串行与并行编排、扇入与扇出模式以及任务状态机模型。理解这些概念是设计可靠补偿机制与链路追踪的前提。
2.1 DAG:任务依赖关系的数学基础
DAG(Directed Acyclic Graph)是任务编排最通用的抽象模型。每个任务是一个节点,任务间的依赖关系是一条有向边,且图中不存在循环依赖。DAG 保证了任务可以按照拓扑顺序执行,不会出现死锁。
下表对比了 DAG 与其他编排模型的适用场景:
| 模型 | 依赖表达力 | 循环支持 | 拓扑排序 | 典型场景 |
|---|---|---|---|---|
| DAG | 强(多对多) | 否 | 必需 | 微服务编排、工作流引擎 |
| 树形 | 弱(一对多) | 否 | 简单 | 批处理任务、MapReduce |
| 有环图 | 强 | 是 | 不支持 | 状态机、重试循环(需特殊处理) |
以下 SVG 展示了一个典型的订单履约 DAG,包含支付、库存、物流三个子流程的依赖关系:
2.2 串行与并行编排
DAG 的边定义了两种基本执行模式:串行(顺序依赖)和并行(无依赖或扇出)。编排引擎需要根据 DAG 的拓扑排序结果,动态决定哪些任务可以并发执行。
2.2.1 串行编排
当任务 B 依赖任务 A 的输出时,必须串行执行。串行链路的补偿顺序与正向执行顺序相反——即“后进先出”的回滚策略。
2.2.2 并行编排
当多个任务之间不存在依赖关系时,引擎可以同时调度它们。并行编排能显著降低整体延迟,但会引入资源竞争和部分失败的复杂性。
以下代码片段展示了使用 CompletableFuture 实现并行编排的典型模式:
// Java 并行编排示例:同时执行库存锁定和支付扣款
CompletableFuture<Boolean> stockFuture = CompletableFuture
.supplyAsync(() -> inventoryService.lock(productId, quantity));
CompletableFuture<Boolean> paymentFuture = CompletableFuture
.supplyAsync(() -> paymentService.charge(orderId, amount));
// 等待所有并行任务完成
CompletableFuture<Void> allOf = CompletableFuture
.allOf(stockFuture, paymentFuture);
// 聚合结果,处理部分失败
allOf.thenAccept(v -> {
boolean stockOk = stockFuture.join();
boolean paymentOk = paymentFuture.join();
if (!stockOk || !paymentOk) {
// 触发补偿:已成功的任务需要回滚
if (stockOk) inventoryService.unlock(productId, quantity);
if (paymentOk) paymentService.refund(orderId, amount);
}
});
2.3 扇入与扇出模式
扇出(Fan-out)和扇入(Fan-in)是并行编排的两种典型拓扑结构,在分布式系统中极为常见。
| 模式 | 定义 | 补偿策略 | 典型场景 |
|---|---|---|---|
| 扇出 | 一个父任务分发到多个子任务并行执行 | 子任务独立补偿,父任务需等待所有子任务完成或失败 | 批量发送通知、多仓库库存查询 |
| 扇入 | 多个子任务的结果汇聚到一个父任务 | 父任务失败时,需补偿所有已成功的子任务 | 聚合报表、多数据源合并 |
下面的 SVG 图展示了扇出与扇入的对比结构:
2.4 任务状态机模型
每个任务实例在生命周期中都会经历一系列状态转换。标准的状态机模型是编排引擎实现幂等补偿和断点续传的基础。
2.4.1 标准状态定义
| 状态 | 含义 | 可转换至 | 补偿行为 |
|---|---|---|---|
| PENDING | 任务已创建,等待调度 | RUNNING, SKIPPED | 无需补偿 |
| RUNNING | 任务正在执行 | SUCCESS, FAILED, TIMEOUT | 需等待最终状态 |
| SUCCESS | 任务执行成功 | COMPENSATING(仅当上游失败时) | 可被补偿 |
| FAILED | 任务执行失败 | RETRYING, COMPENSATED | 触发重试或补偿 |
| COMPENSATING | 正在执行补偿逻辑 | COMPENSATED, COMP_FAILED | 补偿中 |
| COMPENSATED | 补偿成功 | 终态 | 已完成 |
| COMP_FAILED | 补偿失败 | 终态(需人工介入) | 需告警 |
2.4.2 状态转换图
以下 SVG 展示了任务状态机的完整转换路径,重点标注了补偿相关的状态分支:
2.5 综合案例:电商下单 DAG 编排
将上述概念整合到一个实际案例中。以下是一个电商下单场景的 DAG 编排定义,包含串行、并行、扇入扇出以及完整的状态转换:
// 伪代码:电商下单 DAG 编排定义
DAG orderDag = DAG.builder()
.addNode("validate", TaskType.SERIAL) // 校验参数
.addNode("reserveStock", TaskType.PARALLEL) // 锁定库存(扇出到多仓)
.addNode("chargePayment", TaskType.SERIAL) // 扣款
.addNode("delivery", TaskType.PARALLEL) // 物流下单(扇出到多家物流)
.addNode("notify", TaskType.FAN_IN) // 通知用户(扇入)
.addEdge("validate", "reserveStock")
.addEdge("validate", "chargePayment")
.addEdge("reserveStock", "delivery")
.addEdge("chargePayment", "delivery")
.addEdge("delivery", "notify")
.build();
// 补偿策略:逆序补偿,每个节点独立状态机
orderDag.onFailure(node -> {
if (node.getState() == TaskState.SUCCESS) {
node.compensate(); // 触发补偿逻辑
}
});
该案例中:
- validate 为串行前置节点,失败则整个 DAG 终止。
- reserveStock 和 chargePayment 并行执行,互不依赖。
- delivery 依赖库存和支付都成功后执行,属于扇入。
- notify 为最终扇入节点,聚合所有前置结果。
- 任意节点失败时,仅补偿已成功的上游节点,未执行的节点直接标记为 SKIPPED。