2. Temporal 平台基础:Server、Worker、Client 架构与核心概念
Temporal 平台由三个核心组件构成:Temporal Server、Worker 和 Client。三者通过 gRPC 协议通信,共同支撑起可靠的工作流执行引擎。理解这三者的职责边界以及 Workflow 与 Activity 的语义差异,是掌握 Temporal 架构的第一步。
2.1 三大组件架构总览
下图展示了 Temporal 平台的高层组件关系:Client 提交任务,Server 负责状态持久化与调度,Worker 执行实际业务逻辑。
2.2 组件职责详解
| 组件 | 职责 | 状态 | 典型部署 |
|---|---|---|---|
| Temporal Server | 管理 Workflow 生命周期、持久化事件历史、维护任务队列、提供可见性查询 | 有状态(依赖数据库) | 集群部署(3-5 节点) |
| Worker | 轮询 Task Queue,执行 Workflow 与 Activity 代码,上报执行结果 | 无状态 | 水平扩展,按需启动 |
| Client | 通过 SDK 启动 Workflow、发送 Signal、查询状态 | 无状态 | 嵌入应用进程 |
关键设计原则:Worker 不直接持久化任何状态,所有执行进度由 Server 通过 Event History 记录。这使得 Worker 可以随时崩溃、重启或扩缩容,而不会丢失工作流进度。
2.3 Workflow 与 Activity 的核心概念
Workflow 和 Activity 是 Temporal 应用模型的两大基本执行单元,它们在职责、约束和容错方式上有本质区别。
2.3.1 Workflow(工作流)
- 定义:工作流是一个编排流程的协调者,它定义了一系列步骤的执行顺序、重试策略和补偿逻辑。
- 确定性约束:Workflow 代码必须是确定性的——相同的输入和事件历史必须产生完全相同的执行路径。因此,Workflow 内禁止直接调用外部 API、生成随机数、读取系统时间等非确定性操作。
- 执行模型:Workflow 执行被切分为多个决策任务(Decision Task),每次决策都会追加到 Event History 中。Worker 通过重放历史来恢复状态。
- 生命周期:Workflow 可以运行数毫秒到数年,支持挂起、恢复、定时器、信号和查询。
2.3.2 Activity(活动)
- 定义:Activity 是工作流中的实际业务操作,例如调用支付接口、写入数据库、发送邮件等。
- 无约束:Activity 代码没有确定性要求,可以执行任何 I/O 操作、调用第三方服务、生成随机数等。
- 执行模型:Activity 由 Worker 在独立线程或进程中执行,执行结果(成功/失败/超时)通过 Task Token 上报给 Server。
- 重试与超时:Activity 支持独立的重试策略(Retry Policy)和超时配置(StartToCloseTimeout、ScheduleToCloseTimeout 等)。
| 对比维度 | Workflow | Activity |
|---|---|---|
| 主要职责 | 编排与协调 | 执行具体业务逻辑 |
| 确定性要求 | 必须确定性 | 无要求 |
| 可执行 I/O | 禁止直接 I/O | 允许任意 I/O |
| 重试机制 | 通过 Activity 重试间接实现 | 内置独立重试策略 |
| 执行时长 | 可极长(年级) | 通常较短(秒/分钟级) |
| 状态恢复方式 | 事件历史重放 | 心跳 + 结果上报 |
2.4 Workflow 与 Activity 的交互流程
下图展示了一个典型工作流中,Client、Server、Worker 三者如何协作完成一次 Workflow + Activity 的执行。
执行要点:
- Workflow 代码中通过
workflow.ExecuteActivity()调用 Activity,该调用会被序列化为一个命令(Command)追加到 Event History。 - Server 收到命令后,在对应的 Task Queue 中创建一个 Activity Task,等待 Worker 轮询。
- Worker 执行 Activity 时,可以发送心跳(Heartbeat)来报告进度,Server 据此判断 Activity 是否存活。
- Activity 执行完成后,Worker 将结果通过
RespondActivityTaskCompleted上报给 Server,Server 再调度下一个决策任务。
2.5 代码示例:最小 Workflow + Activity
以下是一个使用 Temporal Go SDK 的完整示例,展示 Workflow 如何调用 Activity。
// activity.go
package myapp
import (
"context"
"fmt"
"go.temporal.io/sdk/activity"
)
// GreetActivity 是一个简单的 Activity,返回问候语
func GreetActivity(ctx context.Context, name string) (string, error) {
logger := activity.GetLogger(ctx)
logger.Info("GreetActivity started", "name", name)
return fmt.Sprintf("Hello, %s!", name), nil
}
// workflow.go
package myapp
import (
"time"
"go.temporal.io/sdk/workflow"
)
// GreetWorkflow 是一个工作流,调用 GreetActivity
func GreetWorkflow(ctx workflow.Context, name string) (string, error) {
// 设置 Activity 执行选项:超时 5 秒
ao := workflow.ActivityOptions{
StartToCloseTimeout: 5 * time.Second,
}
ctx = workflow.WithActivityOptions(ctx, ao)
var result string
// 调用 Activity,等待结果
err := workflow.ExecuteActivity(ctx, GreetActivity, name).Get(ctx, &result)
if err != nil {
return "", err
}
return result, nil
}
// worker.go
package main
import (
"log"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
"myapp"
)
func main() {
c, err := client.Dial(client.Options{})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()
w := worker.New(c, "greeting-task-queue", worker.Options{})
w.RegisterWorkflow(myapp.GreetWorkflow)
w.RegisterActivity(myapp.GreetActivity)
err = w.Run(worker.InterruptCh())
if err != nil {
log.Fatalln("Unable to start worker", err)
}
}
// client.go
package main
import (
"context"
"fmt"
"log"
"go.temporal.io/sdk/client"
"myapp"
)
func main() {
c, err := client.Dial(client.Options{})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()
// 启动 Workflow
we, err := c.ExecuteWorkflow(context.Background(),
client.StartWorkflowOptions{
TaskQueue: "greeting-task-queue",
},
myapp.GreetWorkflow, "Temporal")
if err != nil {
log.Fatalln("Unable to execute workflow", err)
}
var result string
err = we.Get(context.Background(), &result)
if err != nil {
log.Fatalln("Workflow failed", err)
}
fmt.Printf("Workflow result: %s\n", result)
}
常见误区:
- 不要在 Workflow 中直接调用 Activity 函数——必须通过
workflow.ExecuteActivity()调用,否则无法保证确定性。 - 不要将 Activity 的结果直接赋值给 Workflow 的局部变量而不使用
.Get()——这会导致异步调用未完成就继续执行。 - Worker 必须同时注册 Workflow 和 Activity 类型,否则 Server 无法找到对应的执行代码。