Temporal Replay 2026 深度实战:从持久化执行范式到 AI Agent 编排新纪元,手写生产级智能工作流引擎
前言:分布式系统的噩梦,Durable Execution 的解药
写分布式系统的工程师都知道,最痛苦的不是写业务逻辑,而是处理失败。
一个简单的用户注册流程:接收请求 → 发验证码 → 写数据库 → 发欢迎邮件。任何一步都可能在任何时刻失败:网络抖动、数据库超时、邮件服务宕机。传统方案靠幂等性、补偿事务、消息队列……代码写出来比业务逻辑还复杂三倍。
Temporal 就是来解决这个问题的。它的核心概念叫 Durable Execution(持久化执行):你的代码执行到哪一步,Temporal 自动帮你记下来。进程崩了?Worker 重启了?网络断了?没关系,Workflow 从上次中断的地方继续跑,就像从未中断过一样。
2026 年 5 月,Temporal 在 Replay 大会上发布了大量重磅新特性,包括 Serverless Workers、Standalone Activities、Workflow Streams、AI 框架深度集成等。本文从工程视角深入剖析这些新特性,手写可运行的 Go 代码,带你真正理解 Temporal 的设计哲学和使用方法。
一、核心概念:Durable Execution 到底是怎么回事
1.1 传统分布式系统的状态困境
让我们用一个具体例子来理解问题。假设你要实现一个转账操作:
// 传统实现——状态在内存里,进程一挂全丢
func Transfer(from, to string, amount float64) error {
// 第一步:从账户扣钱
if err := debitAccount(from, amount); err != nil {
return fmt.Errorf("扣款失败: %w", err)
}
// 第二步:给对方账户加钱
if err := creditAccount(to, amount); err != nil {
// 灾难:钱已经扣了,但没加进去!
// 需要补偿事务来回滚第一步
rollbackDebit(from, amount) // 补偿逻辑
return fmt.Errorf("入账失败: %w", err)
}
// 第三步:记录流水
if err := writeTransactionLog(from, to, amount); err != nil {
log.Errorf("流水记录失败,需人工补录")
}
return nil
}
这个流程里有三种失败场景,每种都需要不同的处理策略。代码越来越复杂,边界情况越来越多,最后维护不动了。
1.2 Temporal 的解法:Workflow + Activity 二层模型
Temporal 把工作分为两层:
Activity(活动):真实世界中有副作用的操作,会失败,会超时,需要重试。数据库操作、HTTP 调用、发邮件——这些都封装成 Activity。Activity 是无状态的,每次调用都是独立的。
Workflow(工作流):纯函数的编排逻辑,本身没有副作用。Workflow 定义做什么,Activity 定义怎么做。Workflow 执行过程会被 Temporal 持久化——每执行一步,状态自动快照。
// Workflow 定义:编排逻辑,纯函数,没有副作用
func TransferWorkflow(ctx workflow.Context, req TransferRequest) (*TransferResult, error) {
var result TransferResult
// 配置 Activity 的超时和重试策略
debitOpts := workflow.ActivityOptions{
StartToCloseTimeout: 10 * time.Second,
RetryPolicy: &temporal.RetryPolicy{
InitialInterval: time.Second,
BackoffCoefficient: 2.0,
MaximumAttempts: 5,
},
}
ctx = workflow.WithActivityOptions(ctx, debitOpts)
// 第一步:扣款(Activity)
var debitResult DebitResult
err := workflow.ExecuteActivity(ctx, DebitAccount, req.From, req.Amount).Get(ctx, &debitResult)
if err != nil {
return nil, fmt.Errorf("扣款失败: %w", err)
}
// 第二步:入账(Activity)
var creditResult CreditResult
err = workflow.ExecuteActivity(ctx, CreditAccount, req.To, req.Amount).Get(ctx, &creditResult)
if err != nil {
// Temporal 会自动重试,因为上面有 RetryPolicy
// 你不需要写补偿逻辑!Temporal 替你记住了第一步执行成功
return nil, fmt.Errorf("入账失败: %w", err)
}
result.Success = true
result.TransactionID = debitResult.TransactionID
return &result, nil
}
关键在于:即使 Worker 进程在执行 CreditAccount 时崩溃,Workflow 也不会重新从头开始。Temporal Server 保存了完整的执行历史,Workflow 重启后会回放(Replay)已执行过的 Activity 结果,直接从 CreditAccount 继续。
1.3 Task Queue:Workflow 与 Worker 的解耦之道
Temporal 的 Task Queue 把 Workflow 逻辑和执行 Worker 解耦了:
// Worker 启动时注册到 Task Queue
worker, err := NewWorker(client, WorkerOptions{
TaskQueue: "payment-tasks",
})
// Workflow 发送任务时,不需要知道谁来处理
// Temporal 自动路由到注册在对应 Task Queue 的 Worker
这意味着:同一个 Task Queue 可以有多个 Worker 实例实现横向扩展,Worker 可以用不同语言实现(Go、Python、Java 共存),Workflow 代码完全不需要关心有多少 Worker 在跑。
二、Replay 2026 新特性一:Serverless Workers——AWS Lambda 上的无服务器执行
2.1 为什么这很重要
传统 Temporal Worker 需要长期运行的进程,需要管理服务器、设计自动扩缩容策略、为峰值负载预留资源。Serverless Workers 彻底改变了这一点:你的 Workflow Worker 可以直接跑在 AWS Lambda 上,Temporal Cloud 自动根据工作负载调用 Lambda,秒级扩缩容,零闲置资源。
2.2 架构解析
传统模式:Lambda 收到请求 → 调用 Temporal → Temporal 执行 Workflow → 结果返回
Serverless Worker 模式:Temporal 按需唤醒 Lambda 作为 Worker 实例,让 Lambda 直接运行 Workflow 代码,Activity 在 Lambda 内部直接执行,减少网络跳数。
// 部署到 Lambda 的 Worker 入口(Go)
package main
import (
"context"
"log"
"github.com/aws/aws-lambda-go/lambda"
"github.com/temporalio/samples-go/serverless/worker"
)
func main() {
h, err := worker.NewServerlessHandler(
worker.WithTaskQueue("serverless-tasks"),
worker.WithNamespace("default"),
)
if err != nil {
log.Fatal(err)
}
// Temporal SDK 内部处理 Lambda 的调用生命周期
lambda.Start(h.Invoke)
}
Serverless Workers 目前处于 Pre-release 阶段,聚焦 AWS Lambda 集成。这对事件驱动型 Workflow、间歇性负载、需要弹性但不想管理基础设施的团队特别有价值。
三、Replay 2026 新特性二:Standalone Activities——独立运行的轻量级持久化任务
3.1 从 Workflow 附属到独立原语
这是 Temporal 历史上第一次把 Activity 从 Workflow 中解耦出来,作为独立原语使用。之前 Activity 必须作为 Workflow 的一个步骤存在。Standalone Activities 允许 Activity 直接独立运行,不需要包裹在 Workflow 里:
// 启动一个独立的 Activity,不需要 Workflow
result, err := client.ExecuteStandaloneActivity(
context.Background(),
ExecuteStandaloneActivityRequest{
ActivityType: "ProcessDataJob",
TaskQueue: "standalone-tasks",
Input: &DataJobInput{
JobID: "job-12345",
Source: "s3://data/raw/",
Destination: "s3://data/processed/",
},
Options: ExecuteStandaloneActivityOptions{
StartToCloseTimeout: 30 * time.Minute,
RetryPolicy: &temporal.RetryPolicy{
MaximumAttempts: 3,
},
},
},
)
当你需要更强的编排能力时,Standalone Activity 可以无缝迁移到 Workflow 里,不需要重写 Activity 代码。
3.2 与普通 Workflow Activity 的对比
| 特性 | Workflow Activity | Standalone Activity |
|---|---|---|
| 是否需要 Workflow | ✅ 需要 | ❌ 不需要 |
| 状态持久化 | ✅ Workflow 历史中 | ✅ 独立状态记录 |
| 自动重试 | ✅ RetryPolicy | ✅ RetryPolicy |
| 编排能力 | Workflow 内调用 | 直接调用 |
| 适用场景 | 有复杂流程的业务 | 独立任务、后台作业、定时同步 |
四、Replay 2026 新特性三:Workflow Streams——为 AI Agent 打造的实时流式交互
4.1 问题:LLM 流式输出的持久化困境
当你让 AI Agent 执行一个复杂任务,LLM 产生的是流式输出。如果用户中途刷新页面,或者 Worker 崩溃了,进度全丢了。Workflow Streams 把流式数据持久化——事件写入 Workflow 历史,客户端可以从断点继续。
4.2 核心原理
Workflow Streams 用 Temporal 的 Signal(单向消息)和 Update(请求/响应)两个原生原语实现:
- Publish(发布):Workflow/Activity/外部客户端向 Workflow 的某个 topic 发送事件 → Temporal 把事件写入 Workflow 历史(持久化)
- Subscribe(订阅):客户端通过 Update 请求从 Workflow 获取事件,如果暂时没有新事件,Update handler 阻塞等待(不是轮询,不浪费资源)
func CodeGenWorkflow(ctx workflow.Context, req CodeGenRequest) (*CodeGenResult, error) {
// 在 Workflow 初始化时创建流
stream, err := NewWorkflowStream(ctx, "code-gen-stream")
if err != nil {
return nil, err
}
// 创建一个 topic 来发布 LLM token
tokenTopic := stream.Topic("tokens", func() interface{} { return &TokenEvent{} })
// 发布初始状态
statusTopic := stream.Topic("status", func() interface{} { return &LLMStatusEvent{} })
statusTopic.Publish(ctx, &LLMStatusEvent{Step: "analyzing", Progress: 0.0})
// 生成代码(模拟 LLM 流式输出)
tokens, err := streamLLMResponse(ctx, req.Prompt, func(token string) error {
// 每个 token 都发布到流,SDK 后台做 batching(100ms 用于 AI 场景)
return tokenTopic.Publish(ctx, &TokenEvent{Token: token, Finished: false})
})
if err != nil {
return nil, err
}
tokenTopic.Publish(ctx, &TokenEvent{Finished: true})
validated, err := validateCode(ctx, tokens)
if err != nil {
return nil, err
}
return &CodeGenResult{Code: validated}, nil
}
4.3 Exactly-Once 语义
Workflow Streams 保证 exactly-once 语义:发布端每次发布带 sequence key,重试时去重;订阅端客户端记录已收到的事件编号,断线重连后从断点继续;多发布者写入同一 topic 时,Workflow 的顺序处理保证全局顺序。
五、Replay 2026 新特性四:External Payload Storage——AI 场景的大文件处理
5.1 问题:Workflow 历史的大小危机
AI 应用场景下,一个 Workflow 可能处理超长上下文(10 万 token 的文档),输出超长结果。Temporal 默认把 payload 存在 Workflow 历史里,历史会越来越大,Replay 速度越来越慢。
5.2 External Storage 架构
External Payload Storage 让 Activity 的输入/输出存在外部存储(S3 或自定义 Driver),Workflow 历史只存引用:
// Activity 读取大文件(输入存在 S3,Workflow 历史只存 S3 URI)
func AnalyzeDocumentActivity(ctx context.Context, input DocumentInput) (*AnalysisResult, error) {
// input.SourceURI = "s3://my-bucket/docs/large-doc.txt"
// Temporal SDK 自动从 S3 拉取内容,不需要 Activity 自己处理 S3
// Activity 代码完全不需要感知存储细节
doc, err := downloadAndParse(ctx, input.SourceURI)
if err != nil {
return nil, err
}
analysis := runAnalysis(doc)
resultURI, err := uploadResult(ctx, "s3://my-bucket/results/output.json", analysis)
return &AnalysisResult{ResultURI: resultURI, Pages: len(doc.Pages)}, nil
}
// 配置 S3 External Storage
worker, err := NewWorker(client, WorkerOptions{
TaskQueue: "ai-tasks",
DataConverter: externalstorage.NewDataConverter(
externalstorage.S3Config{
Bucket: "my-temp-payloads",
Region: "us-east-1",
MaxPayloadMB: 10, // 超过 10MB 自动切换到 S3
},
),
})
六、Replay 2026 新特性五:AI 生态深度集成
6.1 Google ADK 集成
Google Agent Development Kit(ADK)让开发者快速构建 AI Agent。Temporal 的集成让 LLM 调用和工具执行都跑在 Temporal Activities 里,Activity 有重试、有持久化、有超时控制。任何步骤失败 → Temporal 自动重试(RetryPolicy 控制)。重试耗尽 → Workflow 暂停,等待人工介入或告警。
6.2 OpenAI Agents SDK 集成
OpenAI 的 Agents SDK 支持沙箱执行(隔离的代码执行环境)。Temporal 集成让:
- 沙箱会话有持久化(断线重连后继续)
- 沙箱失败有自动重试(不用手动重启)
- 多轮对话有状态管理(Workflow 历史即对话历史)
6.3 AI Agent 编排的完整架构
[客户端 / 前端]
↓ Workflow Streams(实时流)
[Temporal Workflow Engine](AI Agent Workflow)
↓ Activity 执行
[Activity Workers]
┌─────────────────┐ ┌─────────────────┐
│ LLM 调用 │ │ 工具执行 │
│ (Google ADK) │ │ (OpenAI Agents) │
└─────────────────┘ └─────────────────┘
↓
[External Payload Storage (S3)]
七、生产级特性:Worker Versioning 和 Task Queue Priority
7.1 Worker Versioning:零风险的代码更新
Worker Versioning 允许你同时运行多个版本的 Worker,Temporal 自动管理:新启动的 Workflow → 使用新版本 Worker;正在运行的 Workflow → 继续使用启动它的版本;旧 Workflow 跑完后 → 该 Worker 版本可以下线。
worker, err := NewWorker(client, WorkerOptions{
TaskQueue: "payment-tasks",
BuildID: "v2.3.1", // 每次部署用新的 Build ID
UseVersioning: true,
})
// 渐进式发布:先用 10% 流量
worker.UpdateBuildID(escalationPolicy{
Rules: []EscalationRule{
{BuildID: "v2.3.1", Percentage: 10},
{BuildID: "v2.2.0", Percentage: 90}, // 老版本兜底
},
})
7.2 Task Queue Priority & Fairness:多租户资源保障
当多个团队共用一个 Temporal 集群时,高优先级业务可能被低优先级任务抢占。Priority 让你设置优先级级别,Fairness 防止单一租户/团队耗尽资源。
八、端到端实战:构建 AI 数据摄取流水线
8.1 完整代码
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/workflow"
)
// 数据结构定义
type DataIngestionRequest struct {
JobID string
Sources []DataSource
TargetSchema string
LLMPrompt string
}
type DataSource struct {
Type string
Location string
Config map[string]string
}
type DataIngestionResult struct {
JobID string
RecordsTotal int
RecordsSuccess int
RecordsFailed int
Duration time.Duration
Errors []string
}
// Activity 定义
type DataIngestionActivities struct{}
func (a *DataIngestionActivities) FetchFromS3(ctx context.Context, source DataSource) ([]byte, error) {
log.Printf("从 S3 获取数据: %s", source.Location)
data := []byte(fmt.Sprintf(`{"records": [{"id": 1, "name": "Alice"}]}`))
return data, nil
}
func (a *DataIngestionActivities) FetchFromAPI(ctx context.Context, source DataSource) ([]byte, error) {
log.Printf("从 API 获取数据: %s", source.Location)
return []byte(`{"records": [{"id": 2, "name": "Bob"}]}`), nil
}
func (a *DataIngestionActivities) TransformWithLLM(ctx context.Context, data []byte, prompt string) ([]byte, error) {
logger := activity.GetLogger(ctx)
logger.Info("调用 LLM 进行数据转换")
// Activity 有重试机制,LLM 调用失败会自动重试
return []byte(fmt.Sprintf(`{"transformed": %s, "by_llm": true}`, string(data))), nil
}
func (a *DataIngestionActivities) WriteToDatabase(ctx context.Context, data []byte) error {
logger := activity.GetLogger(ctx)
logger.Info("写入目标数据库")
return nil
}
func (a *DataIngestionActivities) ValidateData(ctx context.Context, data []byte) error {
return nil
}
// Workflow 实现(含 Workflow Streams 进度推送)
func DataIngestionWorkflow(ctx workflow.Context, req DataIngestionRequest) (*DataIngestionResult, error) {
// 配置 Activity 超时和重试策略
activityOpts := workflow.ActivityOptions{
StartToCloseTimeout: 10 * time.Minute,
HeartbeatTimeout: 30 * time.Second,
RetryPolicy: &temporal.RetryPolicy{
InitialInterval: 5 * time.Second,
BackoffCoefficient: 2.0,
MaximumAttempts: 3,
NonRetryableErrorTypes: []string{"ValidationError", "AuthenticationError"},
},
}
ctx = workflow.WithActivityOptions(ctx, activityOpts)
ctx = workflow.WithCancel(ctx)
ctx = workflow.WithWorkflowTimeout(ctx, 1*time.Hour)
result := &DataIngestionResult{JobID: req.JobID, Errors: []string{}}
startTime := time.Now()
activities := &DataIngestionActivities{}
// 步骤 1:并发从所有数据源获取数据
publishProgress(ctx, "initialized", 0.0, "开始数据摄取")
publishProgress(ctx, "fetching", 0.1, fmt.Sprintf("从 %d 个数据源获取数据", len(req.Sources)))
fetchedData := make([][]byte, len(req.Sources))
fetchPromises := make([]workflow.Future, len(req.Sources))
for i, source := range req.Sources {
switch source.Type {
case "s3":
fetchPromises[i] = workflow.ExecuteActivity(ctx, activities.FetchFromS3, source)
default:
fetchPromises[i] = workflow.ExecuteActivity(ctx, activities.FetchFromAPI, source)
}
}
for i, future := range fetchPromises {
var data []byte
if err := future.Get(ctx, &data); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("数据源 %d 获取失败: %v", i, err))
continue
}
fetchedData[i] = data
}
mergedData := mergeData(fetchedData)
publishProgress(ctx, "fetching_complete", 0.3, fmt.Sprintf("获取完成,共 %d 条原始记录", len(mergedData)))
// 步骤 2:LLM 数据清洗
publishProgress(ctx, "transforming", 0.4, "LLM 数据转换中")
var transformedData []byte
err := workflow.ExecuteActivity(ctx, activities.TransformWithLLM, mergedData, req.LLMPrompt).Get(ctx, &transformedData)
if err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("LLM 转换失败: %v", err))
transformedData = []byte(`{"transformed": []}`) // 降级路径
}
publishProgress(ctx, "transforming_complete", 0.7, "LLM 转换完成")
// 步骤 3:数据验证
publishProgress(ctx, "validating", 0.75, "数据验证中")
if err := workflow.ExecuteActivity(ctx, activities.ValidateData, transformedData).Get(ctx, nil); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("数据验证失败: %v", err))
}
// 步骤 4:写入数据库
publishProgress(ctx, "writing", 0.85, "写入目标数据库")
if err := workflow.ExecuteActivity(ctx, activities.WriteToDatabase, transformedData).Get(ctx, nil); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("数据库写入失败: %v", err))
return nil, fmt.Errorf("写入失败: %w", err)
}
result.Duration = time.Since(startTime)
result.RecordsTotal = len(mergedData) / 50
result.RecordsSuccess = result.RecordsTotal - len(result.Errors)
result.RecordsFailed = len(result.Errors)
publishProgress(ctx, "completed", 1.0, "数据摄取完成")
return result, nil
}
// 进度推送
type ProgressEvent struct {
Phase string `json:"phase"`
Progress float64 `json:"progress"`
Message string `json:"message"`
Timestamp int64 `json:"timestamp"`
}
func publishProgress(ctx workflow.Context, phase string, progress float64, message string) {
event := ProgressEvent{Phase: phase, Progress: progress, Message: message, Timestamp: time.Now().Unix()}
data, _ := json.Marshal(event)
workflow.SignalNamed(ctx, "workflow-progress", data)
}
func mergeData(dataSets [][]byte) []byte {
merged := "["
for i, data := range dataSets {
if i > 0 { merged += "," }
merged += string(data)
}
merged += "]"
return []byte(merged)
}
8.2 关键设计点解析
失败模式设计:数据源获取失败不导致整个 Workflow 失败;LLM 转换失败有降级路径(规则引擎替代);只有数据库写入失败才返回错误。三个级别的容错满足不同业务需求。
并发数据获取:所有数据源并发获取,用 workflow.Future 等待结果。数据源数量不影响代码复杂度。
进度推送:通过 Workflow Signal 实现进度推送,即使 Worker 崩溃,Workflow 重启后可以继续推送进度。
九、运行环境和最佳实践
快速本地体验
# 1. 安装 Temporal CLI
brew install temporal
# 2. 启动本地开发服务器
temporal server start-dev
# 3. 浏览器打开 http://localhost:8233 查看 Web UI
# 4. 运行示例
go run .
生产部署要点
- Activity 并发度 = Worker 数量 ×
MaxConcurrentActivityExecutionSize - 每个 Task Queue 至少 2 个 Worker 实例(故障切换)
StartToCloseTimeout设置为正常耗时的 3-5 倍- 长时间 Activity 必须设置
HeartbeatTimeout,否则无法感知 Worker 崩溃 NonRetryableErrorTypes是关键:验证错误和认证错误不需要重试,加快失败速度
十、与其他方案的横向对比
Temporal vs. 传统消息队列
| 维度 | RabbitMQ / Kafka | Temporal |
|---|---|---|
| 失败处理 | 需要补偿事务 | 自动重试 + 状态持久化 |
| 代码复杂度 | 高(状态机自己写) | 低(纯函数 + Activity) |
| 调试能力 | 差(日志碎片化) | 强(完整执行历史) |
| 可视化 | 外部工具 | 内置 Web UI |
| 多语言支持 | 好 | 好 |
| 状态查询 | 需要自己实现 | 内置 Query |
Temporal vs. 朴素 CRON + 脚本
CRON 脚本失败需要自己写监控和重试,Temporal CRON 自动重试、自动记录状态、可观测。脚本状态丢失,Temporal Workflow 状态持久化。
十一、总结与展望
Temporal 解决了什么问题
Temporal 的本质是把容错从业务代码中抽离出来。Replay 2026 的更新让 Temporal 的能力边界进一步扩展:
| 特性 | 解决的问题 |
|---|---|
| Serverless Workers | 不想管基础设施 → 直接上 Lambda |
| Standalone Activities | 轻量任务不需要 Workflow 包装 |
| Workflow Streams | AI 流式输出如何持久化 |
| External Payload Storage | AI payload 太大撑爆历史 |
| Google ADK / OpenAI 集成 | Agent 工具调用如何可靠化 |
| Worker Versioning | 发布新版本不怕跑着的老 Workflow |
| Task Queue Priority | 多团队共用集群的公平性 |
AI Agent 时代的 Temporal
2026 年的 AI Agent 浪潮里,Temporal 正在成为 AI Agent 的基础设施层:LLM 不可靠 → Activity 包装调用失败自动重试;Agent 的状态如何管理 → Workflow 历史等于 Agent 记忆;流式输出如何持久化 → Workflow Streams;大量文档如何处理 → External Payload Storage;多 Agent 如何协作 → Child Workflow 嵌套 + Signal 通信。
什么时候用 Temporal
适合用 Temporal:有复杂的多步骤业务流程(超过 3 步)、每一步都可能失败且失败后需要正确的重试行为、需要完整的执行历史(审计、调试、补偿)、构建 AI Agent 或需要流式交互的应用、需要跨多个服务的事务性保障。
不需要 Temporal:简单的微服务调用(直接 gRPC/HTTP)、纯异步消息处理(Kafka + 消费者足够)、无状态的数据处理流水线(Airflow 更合适)。
Replay 2026 之后 Temporal 的演进方向
从 Replay 2026 来看,Temporal 的重心在三个方向:Serverless-first(让能力无缝延伸到 FaaS 环境)、AI-native(围绕 LLM 和 Agent 的特殊需求设计新原语)、企业级治理(多租户隔离、计费 API、SCIM、优先级保障)。
这三个方向也代表了分布式系统编排领域的演进方向:从基础设施到应用智能。
本文基于 Temporal 官方文档、Replay 2026 大会公告及 Temporal Go SDK 架构分析撰写。所有代码示例基于 Temporal SDK 架构原理编写,API 可能随版本迭代有所调整,请以官方文档(https://docs.temporal.io)为准。