1. Temporal 基础概念:工作流、活动、任务队列与工作流执行
Temporal 是一个分布式、可扩展、持久化的工作流编排引擎。要理解其 DAG 边界与数据一致性保障,首先需要掌握四个最核心的抽象概念:工作流 (Workflow)、活动 (Activity)、任务队列 (Task Queue) 和 工作流执行 (Workflow Execution)。它们共同构成了 Temporal 应用的基本运行单元与通信模型。
1.1 核心概念定义
| 概念 | 英文 | 定义 | 类比(现实世界) |
|---|---|---|---|
| 工作流 | Workflow | 一段可执行的代码逻辑,用于编排多个步骤(活动)的执行顺序、重试策略、超时设置以及补偿逻辑。工作流代码必须是确定性的(Deterministic)。 | 一份详细的商业合同或项目流程图,规定了“先做什么,后做什么”。 |
| 活动 | Activity | 工作流中一个具体的、有状态的业务操作单元。活动可以包含非确定性代码(如网络调用、数据库读写、文件操作)。 | 合同中的一条具体条款执行,例如“调用支付网关扣款”或“发送确认邮件”。 |
| 任务队列 | Task Queue | 一个逻辑上的缓冲通道,用于将工作流任务(Workflow Task)和活动任务(Activity Task)分发给对应的 Worker 进程。 | 公司内部的“任务分派箱”,不同部门(Worker)从对应的箱子里领取任务。 |
| 工作流执行 | Workflow Execution | 工作流代码的一次具体运行实例。每个执行实例都有唯一的标识符(Workflow ID + Run ID),并拥有独立的执行状态和历史记录。 | 一份合同从签署到履行完毕的完整生命周期。 |
1.2 概念关系与执行流程
这四个概念在 Temporal 系统中通过 Worker 和 Server 的协作紧密关联。下图展示了从工作流定义到一次完整执行的核心交互关系:
1.3 关键关系详解
核心原则:工作流负责“编排”,活动负责“执行”。工作流代码必须是确定性的,以确保在任何 Worker 上重放(Replay)时都能得到相同的结果;而活动代码则没有此限制,可以安全地调用外部服务或执行 I/O 操作。
1.3.1 工作流与活动的关系
- 编排与执行分离:工作流通过
ExecuteActivity或ExecuteLocalActivity调用活动。工作流本身不执行具体的业务逻辑,只负责决定“何时”以及“在何种条件下”调用活动。 - 超时与重试:工作流可以为每个活动单独配置 Schedule-To-Close Timeout、Start-To-Close Timeout 和 Heartbeat Timeout。当活动失败时,工作流可以根据预设的重试策略(Retry Policy)自动重新调度活动。
- 结果传递:活动的返回值(Result)会被 Temporal Server 持久化,并作为事件写入工作流的历史记录中。工作流代码在
await活动结果时,实际上是在等待该事件的出现。
1.3.2 任务队列的桥梁作用
- 任务类型:任务队列承载两种类型的任务:Workflow Task(驱动工作流代码执行)和 Activity Task(驱动活动代码执行)。
- 路由机制:Worker 在启动时会注册自己监听哪些任务队列。Temporal Server 根据任务队列名称将任务分发给对应的 Worker。这实现了 Worker 与任务之间的解耦。
- 负载均衡:多个 Worker 可以监听同一个任务队列,Server 通过轮询(Poll)机制实现任务的负载均衡分发。
1.3.3 工作流执行的唯一性
- 标识符:每个工作流执行由
Workflow ID和Run ID共同唯一标识。Workflow ID 由业务方指定(如订单ID),Run ID 由 Server 自动生成。 - 执行状态:工作流执行具有明确的状态机:
Running、Completed、Failed、TimedOut、Terminated、ContinuedAsNew和Canceled。 - 事件历史:工作流执行的每一次状态变更、每一个活动的调度与完成,都会被记录为一条不可变的事件(Event),追加到该执行实例的事件历史(Event History)中。这是 Temporal 实现可靠性和可恢复性的基石。
1.4 代码示例:工作流与活动的定义
以下是一个使用 Java SDK 的简单示例,展示如何定义一个工作流和一个活动,并建立它们之间的调用关系。
// 1. 定义活动接口
public interface PaymentActivity {
String processPayment(String orderId, double amount);
}
// 2. 实现活动逻辑(非确定性代码)
public class PaymentActivityImpl implements PaymentActivity {
@Override
public String processPayment(String orderId, double amount) {
// 这里可以安全地调用外部支付网关
// 例如:httpClient.post(paymentGatewayUrl, payload);
return "payment_success_" + orderId;
}
}
// 3. 定义工作流接口
public interface OrderWorkflow {
void startOrderProcessing(String orderId);
}
// 4. 实现工作流逻辑(确定性代码)
public class OrderWorkflowImpl implements OrderWorkflow {
private final PaymentActivity paymentActivity =
Workflow.newActivityStub(PaymentActivity.class,
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofSeconds(10))
.setRetryOptions(RetryOptions.newBuilder()
.setMaximumAttempts(3)
.build())
.build());
@Override
public void startOrderProcessing(String orderId) {
// 工作流编排:调用活动
String paymentResult = paymentActivity.processPayment(orderId, 99.99);
// 根据活动结果决定后续流程
if (paymentResult.startsWith("payment_success")) {
// 可以继续调用其他活动,如发货
} else {
// 执行补偿逻辑
}
}
}
最佳实践:工作流代码中不应包含任何 I/O 操作、线程睡眠、随机数生成或系统时间获取。所有非确定性操作必须封装在活动中。这是保证工作流重放安全性的关键。
1.5 概念对比总结
| 对比维度 | 工作流 (Workflow) | 活动 (Activity) |
|---|---|---|
| 代码确定性 | 必须严格确定性 | 无限制(可包含非确定性代码) |
| 执行时长 | 可长达数月甚至数年 | 通常较短(秒级到分钟级) |
| 状态持久化 | 通过事件历史自动持久化 | 仅结果和错误信息被持久化 |
| 重试机制 | 工作流任务失败由 Server 自动重试 | 由工作流通过 RetryOptions 配置 |
| 主要职责 | 编排、决策、协调 | 执行具体的业务逻辑 |