Skip to content

核心概念 - Activity(活动) ​

活动(Activity)是工作流中执行的单个原子操作。与工作流不同,活动可以包含任何非确定性代码,如网络调用、数据库操作、文件 I/O 等。本文档详细介绍 Temporal 活动的核心概念和最佳实践。


Activity Definition(活动定义) ​

活动定义是一个普通函数,执行具体的业务逻辑。活动可以使用标准 Go 代码,不受确定性约束的限制。

基本定义 ​

go
package activity

import (
    "context"
    "fmt"
)

// 简单的活动定义
func SayHello(ctx context.Context, name string) (string, error) {
    return fmt.Sprintf("Hello, %s!", name), nil
}

// 无返回值的活动
func SendEmail(ctx context.Context, to, subject, body string) error {
    // 发送邮件逻辑
    return nil
}

// 多参数的活动
func ProcessPayment(ctx context.Context, orderID string, amount float64, currency string) error {
    // 处理支付逻辑
    return nil
}

活动参数和返回值 ​

go
// 基本类型参数
func BasicActivity(ctx context.Context, id string, count int, enabled bool) error {
    return nil
}

// 结构体参数
type PaymentRequest struct {
    OrderID  string
    Amount   float64
    Currency string
    Metadata map[string]string
}

type PaymentResult struct {
    TransactionID string
    Status        string
    ProcessedAt   time.Time
}

func ProcessPaymentActivity(ctx context.Context, req PaymentRequest) (PaymentResult, error) {
    // 处理支付
    return PaymentResult{
        TransactionID: "txn-123",
        Status:        "completed",
        ProcessedAt:   time.Now(),
    }, nil
}

// 多返回值
func DivideActivity(ctx context.Context, a, b int) (quotient, remainder int, err error) {
    if b == 0 {
        return 0, 0, fmt.Errorf("division by zero")
    }
    return a / b, a % b, nil
}

活动注册 ​

go
package main

import (
    "go.temporal.io/sdk/client"
    "go.temporal.io/sdk/worker"
    "myapp/activity"
)

func main() {
    c, _ := client.Dial(client.Options{})
    defer c.Close()

    w := worker.New(c, "my-task-queue", worker.Options{})
    
    // 方式一:注册活动函数
    w.RegisterActivity(activity.SayHello)
    w.RegisterActivity(activity.ProcessPayment)
    
    // 方式二:注册活动结构体方法
    w.RegisterActivity(&PaymentActivity{})
    
    w.Run(worker.InterruptCh())
}

结构体活动 ​

go
// 使用结构体组织相关活动
type PaymentActivity struct {
    PaymentGateway PaymentGateway
    Logger         *zap.Logger
}

// 结构体方法作为活动
func (a *PaymentActivity) ProcessPayment(ctx context.Context, req PaymentRequest) (PaymentResult, error) {
    a.Logger.Info("Processing payment", "orderID", req.OrderID)
    return a.PaymentGateway.Process(req)
}

func (a *PaymentActivity) RefundPayment(ctx context.Context, transactionID string) error {
    a.Logger.Info("Refunding payment", "transactionID", transactionID)
    return a.PaymentGateway.Refund(transactionID)
}

// 注册结构体活动
func main() {
    w := worker.New(c, "task-queue", worker.Options{})
    
    paymentActivity := &PaymentActivity{
        PaymentGateway: NewPaymentGateway(),
        Logger:         zap.NewExample(),
    }
    
    w.RegisterActivity(paymentActivity)
}

Activity Execution(活动执行) ​

活动由工作流调用执行,执行结果会被持久化到事件历史中。

从工作流调用活动 ​

go
func OrderWorkflow(ctx workflow.Context, orderID string) error {
    // 配置活动选项
    ao := workflow.ActivityOptions{
        StartToCloseTimeout: 5 * time.Minute,
    }
    ctx = workflow.WithActivityOptions(ctx, ao)
    
    // 调用活动
    var result string
    err := workflow.ExecuteActivity(ctx, ProcessOrderActivity, orderID).Get(ctx, &result)
    if err != nil {
        return err
    }
    
    return nil
}

活动执行流程 ​

┌─────────────────────────────────────────────────────────────────────────┐
│                        Activity Execution Flow                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Workflow Worker                    Temporal Server                     │
│  ┌─────────────┐                   ┌─────────────┐                      │
│  │ Workflow    │                   │  Matching   │                      │
│  │ Execution   │                   │  Service    │                      │
│  └──────┬──────┘                   └──────┬──────┘                      │
│         │                                 │                             │
│         │ 1. Schedule Activity             │                             │
│         │ ──────────────────────────────▶ │                             │
│         │                                 │                             │
│         │                                 │ 2. Add to Task Queue        │
│         │                                 │                             │
│         │                                 ▼                             │
│         │                          ┌─────────────┐                      │
│         │                          │ Task Queue  │                      │
│         │                          └─────────────┘                      │
│         │                                 │                             │
│         │                                 │ 3. Dispatch to Worker       │
│         │                                 ▼                             │
│         │                          ┌─────────────┐                      │
│         │                          │  Activity   │                      │
│         │                          │   Worker    │                      │
│         │                          └──────┬──────┘                      │
│         │                                 │                             │
│         │                                 │ 4. Execute Activity        │
│         │                                 │                             │
│         │ 5. Report Result               │                             │
│         │ ◀────────────────────────────── │                             │
│         │                                 │                             │
│         ▼                                 ▼                             │
│  ┌─────────────────────────────────────────────────────────┐            │
│  │                    Event History                         │            │
│  │  - ActivityTaskScheduled                                │            │
│  │  - ActivityTaskStarted                                  │            │
│  │  - ActivityTaskCompleted                                │            │
│  └─────────────────────────────────────────────────────────┘            │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

执行选项详解 ​

go
ao := workflow.ActivityOptions{
    // 超时设置
    ScheduleToCloseTimeout: 10 * time.Minute,  // 从调度到完成的总超时
    ScheduleToStartTimeout:  1 * time.Minute,   // 从调度到开始执行的超时
    StartToCloseTimeout:     5 * time.Minute,  // 从开始执行到完成的超时
    HeartbeatTimeout:        30 * time.Second, // 心跳超时
    
    // 重试策略
    RetryPolicy: &temporal.RetryPolicy{
        InitialInterval:    time.Second,        // 初始重试间隔
        BackoffCoefficient: 2.0,                // 退避系数
        MaximumInterval:    time.Minute,       // 最大重试间隔
        MaximumAttempts:    5,                 // 最大重试次数
        NonRetryableErrorTypes: []string{      // 不可重试的错误类型
            "InvalidInput",
            "AuthenticationFailed",
        },
    },
    
    // 其他选项
    TaskQueue:                "custom-task-queue",  // 指定任务队列
    ActivityID:               "unique-activity-id", // 活动唯一ID
    WaitForCancellation:      true,                  // 取消时等待活动完成
    OriginalTaskQueueName:   "original-queue",     // 原始任务队列
    
    // 心跳超时配置
    HeartbeatTimeout: 30 * time.Second,
}

ctx = workflow.WithActivityOptions(ctx, ao)

超时类型对比 ​

┌─────────────────────────────────────────────────────────────────────────┐
│                        Activity Timeout Types                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ScheduleToStartTimeout    ScheduleToCloseTimeout                       │
│  ┌────────────────┐        ┌──────────────────────────────────────┐     │
│  │                │        │                                      │     │
│  │   Schedule     │        │   Schedule        Complete           │     │
│  │      │         │        │      │                               │     │
│  │      ▼         │        │      ▼                               │     │
│  │   [Queue]──────│───────▶│   [Queue]──▶[Execute]──▶[Done]     │     │
│  │                │        │                                      │     │
│  └────────────────┘        └──────────────────────────────────────┘     │
│                                                                          │
│  StartToCloseTimeout                                                     │
│  ┌────────────────────────────────┐                                     │
│  │                                │                                     │
│  │   Start              Close     │                                     │
│  │     │                  │       │                                     │
│  │     ▼                  ▼       │                                     │
│  │   [Execute]──────▶[Done]      │                                     │
│  │                                │                                     │
│  └────────────────────────────────┘                                     │
│                                                                          │
│  HeartbeatTimeout                                                        │
│  ┌────────────────────────────────────────────────────────┐             │
│  │  [Start]──▶[Heartbeat]──▶[Heartbeat]──▶...──▶[Done]   │             │
│  │              │              │                          │             │
│  │              └──必须在此时间内发送下一个心跳──┘           │             │
│  └────────────────────────────────────────────────────────┘             │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

异步执行 ​

go
func AsyncWorkflow(ctx workflow.Context) error {
    ao := workflow.ActivityOptions{
        StartToCloseTimeout: time.Minute,
    }
    ctx = workflow.WithActivityOptions(ctx, ao)
    
    // 异步启动活动,不等待完成
    future := workflow.ExecuteActivity(ctx, LongRunningActivity, "input")
    
    // 可以继续执行其他逻辑
    workflow.GetLogger(ctx).Info("Activity started")
    
    // 稍后等待结果
    var result string
    err := future.Get(ctx, &result)
    if err != nil {
        return err
    }
    
    return nil
}

重试策略配置 ​

Temporal 提供灵活的重试策略,活动失败后会自动重试。

重试策略配置 ​

go
retryPolicy := &temporal.RetryPolicy{
    // 初始重试间隔
    InitialInterval: time.Second,
    
    // 退避系数:每次重试间隔 = 前一次间隔 * 退避系数
    BackoffCoefficient: 2.0,
    
    // 最大重试间隔(避免指数增长过大)
    MaximumInterval: time.Minute,
    
    // 最大重试次数
    MaximumAttempts: 5,
    
    // 不可重试的错误类型
    NonRetryableErrorTypes: []string{
        "InvalidInput",
        "AuthenticationFailed",
        "ResourceNotFound",
    },
}

重试间隔计算 ​

重试次数    间隔计算                  实际间隔
────────────────────────────────────────────────
第 1 次     InitialInterval          1s
第 2 次     1s × 2.0                  2s
第 3 次     2s × 2.0                  4s
第 4 次     4s × 2.0                  8s
第 5 次     8s × 2.0 = 16s            16s
第 6 次     16s × 2.0 = 32s           32s
第 7 次     32s × 2.0 = 64s           60s (MaximumInterval)
第 8 次     60s                       60s (MaximumInterval)

在活动中指定错误类型 ​

go
func ProcessPaymentActivity(ctx context.Context, req PaymentRequest) error {
    logger := activity.GetLogger(ctx)
    
    // 业务逻辑
    err := validateRequest(req)
    if err != nil {
        // 不可重试的错误
        return temporal.NewNonRetryableApplicationError(
            err.Error(),
            "InvalidInput",
            nil,
        )
    }
    
    // 可重试的错误(如网络问题)
    err = callPaymentGateway(req)
    if err != nil {
        if isNetworkError(err) {
            // 可重试,直接返回错误
            return err
        }
        // 不可重试
        return temporal.NewNonRetryableApplicationError(
            err.Error(),
            "PaymentFailed",
            nil,
        )
    }
    
    return nil
}

自定义重试策略示例 ​

go
// 不同场景使用不同重试策略
func WorkflowWithDifferentRetryPolicies(ctx workflow.Context) error {
    // 支付活动:快速失败,不重试
    paymentAO := workflow.ActivityOptions{
        StartToCloseTimeout: time.Minute,
        RetryPolicy: &temporal.RetryPolicy{
            MaximumAttempts: 1,  // 不重试
        },
    }
    ctx1 := workflow.WithActivityOptions(ctx, paymentAO)
    workflow.ExecuteActivity(ctx1, ProcessPayment, payment).Get(ctx, nil)
    
    // API 调用:适度重试
    apiAO := workflow.ActivityOptions{
        StartToCloseTimeout: 5 * time.Minute,
        RetryPolicy: &temporal.RetryPolicy{
            InitialInterval:    5 * time.Second,
            BackoffCoefficient: 2.0,
            MaximumAttempts:    3,
        },
    }
    ctx2 := workflow.WithActivityOptions(ctx, apiAO)
    workflow.ExecuteActivity(ctx2, CallExternalAPI, data).Get(ctx, nil)
    
    // 数据同步:激进重试
    syncAO := workflow.ActivityOptions{
        StartToCloseTimeout: 30 * time.Minute,
        RetryPolicy: &temporal.RetryPolicy{
            InitialInterval:    time.Second,
            BackoffCoefficient: 1.5,
            MaximumInterval:    time.Minute,
            MaximumAttempts:    20,
        },
    }
    ctx3 := workflow.WithActivityOptions(ctx, syncAO)
    workflow.ExecuteActivity(ctx3, SyncData, source).Get(ctx, nil)
    
    return nil
}

心跳(Heartbeat) ​

对于长时间运行的活动,心跳用于报告活动仍在执行,防止被误判为超时。

心跳机制 ​

┌─────────────────────────────────────────────────────────────────────────┐
│                        Activity Heartbeat Flow                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Activity Worker                                                        │
│  ┌─────────────────────────────────────────────────────────────┐       │
│  │                                                               │       │
│  │  func LongRunningActivity(ctx context.Context) error {       │       │
│  │      for i := 0; i < 100; i++ {                              │       │
│  │          // 处理任务                                         │       │
│  │          processItem(i)                                       │       │
│  │                                                              │       │
│  │          // 发送心跳                                         │       │
│  │          activity.RecordHeartbeat(ctx, i)                   │       │
│  │      }                                                       │       │
│  │      return nil                                              │       │
│  │  }                                                           │       │
│  │                                                               │       │
│  └──────────────────────────┬──────────────────────────────────┬─┘       │
│                             │                                  │         │
│                             │ Heartbeat                        │ Result  │
│                             ▼                                  ▼         │
│                    ┌──────────────────────────────────────────────┐      │
│                    │          Temporal Server                    │      │
│                    │  ┌────────────────────────────────────────┐ │      │
│                    │  │ HeartbeatTimeout: 30s                 │ │      │
│                    │  │ LastHeartbeat: 2024-01-01 10:00:25   │ │      │
│                    │  │ Status: Running                        │ │      │
│                    │  └────────────────────────────────────────┘ │      │
│                    └──────────────────────────────────────────────┘      │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

实现心跳 ​

go
func LongRunningActivity(ctx context.Context, taskID string) error {
    logger := activity.GetLogger(ctx)
    
    items := getItems(taskID)
    total := len(items)
    
    for i, item := range items {
        // 检查是否被取消
        if activity.IsCancelRequested(ctx) {
            logger.Info("Activity cancelled")
            return activity.NewCanceledError()
        }
        
        // 处理项目
        err := processItem(item)
        if err != nil {
            return err
        }
        
        // 报告心跳和进度
        progress := (i + 1) * 100 / total
        activity.RecordHeartbeat(ctx, progress)
        logger.Info("Progress", "percent", progress)
    }
    
    logger.Info("Task completed", "taskID", taskID)
    return nil
}

心跳详情(恢复进度) ​

go
func ResumableActivity(ctx context.Context, taskID string) error {
    logger := activity.GetLogger(ctx)
    
    // 获取上次心跳详情(用于恢复进度)
    var lastProcessedIndex int
    if activity.HasHeartbeatDetails(ctx) {
        activity.GetHeartbeatDetails(ctx, &lastProcessedIndex)
        logger.Info("Resuming from", "index", lastProcessedIndex)
    }
    
    items := getItems(taskID)
    
    for i := lastProcessedIndex; i < len(items); i++ {
        item := items[i]
        
        // 处理项目
        if err := processItem(item); err != nil {
            return err
        }
        
        // 记录进度(下次恢复时从这里开始)
        activity.RecordHeartbeat(ctx, i+1)
    }
    
    return nil
}

心跳超时配置 ​

go
func WorkflowWithHeartbeat(ctx workflow.Context) error {
    ao := workflow.ActivityOptions{
        StartToCloseTimeout: 10 * time.Minute,  // 活动总超时
        HeartbeatTimeout:     30 * time.Second,  // 心跳超时
    }
    ctx = workflow.WithActivityOptions(ctx, ao)
    
    return workflow.ExecuteActivity(ctx, LongRunningActivity, "task-123").Get(ctx, nil)
}

心跳最佳实践 ​

go
func BestPracticeHeartbeatActivity(ctx context.Context, taskID string) error {
    logger := activity.GetLogger(ctx)
    
    // 1. 定期发送心跳
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
    
    // 2. 获取恢复点
    var lastProcessedIndex int
    if activity.HasHeartbeatDetails(ctx) {
        activity.GetHeartbeatDetails(ctx, &lastProcessedIndex)
    }
    
    items := getItems(taskID)
    
    for i := lastProcessedIndex; i < len(items); i++ {
        select {
        case <-ticker.C:
            // 定期发送心跳
            activity.RecordHeartbeat(ctx, i)
            
        default:
            // 继续处理
        }
        
        // 3. 检查取消请求
        if activity.IsCancelRequested(ctx) {
            // 保存进度
            activity.RecordHeartbeat(ctx, i)
            return activity.NewCanceledError()
        }
        
        // 处理项目
        if err := processItem(items[i]); err != nil {
            // 4. 错误时也保存进度
            activity.RecordHeartbeat(ctx, i)
            return err
        }
    }
    
    return nil
}

幂等性 ​

活动可能被多次执行,因此必须实现幂等性,确保重复执行不会产生副作用。

为什么需要幂等性 ​

┌─────────────────────────────────────────────────────────────────────────┐
│                     Activity Execution Scenarios                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  场景 1:网络超时重试                                                    │
│  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐        │
│  │  Activity   │ ──────▶ │  External   │         │  Activity   │        │
│  │  Execution  │         │   Service   │         │   Result    │        │
│  └─────────────┘         └─────────────┘         └─────────────┘        │
│         │                      │                       ▲                 │
│         │                      │                       │                 │
│         │   ◀──── 超时 ────▶   │                       │                 │
│         │                      │                       │                 │
│         │                      │     重试执行          │                 │
│         └──────────────────────┼───────────────────────┘                 │
│                                │                                          │
│  场景 2:Worker 崩溃                                                     │
│  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐        │
│  │  Activity   │         │   Worker    │         │  New Worker │        │
│  │  Started    │ ──────▶ │   Crashes   │         │   Retries   │        │
│  └─────────────┘         └─────────────┘         └─────────────┘        │
│                                                                          │
│  场景 3:心跳超时                                                        │
│  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐        │
│  │  Activity   │         │  Heartbeat  │         │   Retries   │        │
│  │  Running    │ ──────▶ │   Timeout   │ ──────▶ │   Again     │        │
│  └─────────────┘         └─────────────┘         └─────────────┘        │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

幂等性实现策略 ​

1. 使用唯一标识符 ​

go
func ProcessPaymentActivity(ctx context.Context, req PaymentRequest) error {
    logger := activity.GetLogger(ctx)
    
    // 使用唯一事务ID
    transactionID := req.TransactionID
    
    // 检查是否已处理
    processed, err := checkTransactionProcessed(transactionID)
    if err != nil {
        return err
    }
    if processed {
        logger.Info("Transaction already processed", "transactionID", transactionID)
        return nil
    }
    
    // 处理支付
    err = processPayment(req)
    if err != nil {
        return err
    }
    
    // 标记为已处理
    return markTransactionProcessed(transactionID)
}

2. 使用心跳详情 ​

go
func IdempotentActivity(ctx context.Context, req Request) error {
    logger := activity.GetLogger(ctx)
    
    // 获取上次执行进度
    var progress struct {
        Step1Completed bool
        Step2Completed bool
        Step3Completed bool
    }
    
    if activity.HasHeartbeatDetails(ctx) {
        activity.GetHeartbeatDetails(ctx, &progress)
    }
    
    // 第一步
    if !progress.Step1Completed {
        if err := step1(req); err != nil {
            return err
        }
        progress.Step1Completed = true
        activity.RecordHeartbeat(ctx, progress)
    }
    
    // 第二步
    if !progress.Step2Completed {
        if err := step2(req); err != nil {
            return err
        }
        progress.Step2Completed = true
        activity.RecordHeartbeat(ctx, progress)
    }
    
    // 第三步
    if !progress.Step3Completed {
        if err := step3(req); err != nil {
            return err
        }
        progress.Step3Completed = true
        activity.RecordHeartbeat(ctx, progress)
    }
    
    return nil
}

3. 数据库事务 ​

go
func DatabaseActivity(ctx context.Context, req Request) error {
    logger := activity.GetLogger(ctx)
    
    // 使用数据库事务确保幂等性
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()
    
    // 检查是否已处理
    var exists bool
    err = tx.QueryRow(
        "SELECT EXISTS(SELECT 1 FROM processed_requests WHERE request_id = $1)",
        req.ID,
    ).Scan(&exists)
    if err != nil {
        return err
    }
    
    if exists {
        logger.Info("Request already processed", "requestID", req.ID)
        return nil
    }
    
    // 处理请求
    err = processRequest(tx, req)
    if err != nil {
        return err
    }
    
    // 记录已处理
    _, err = tx.Exec(
        "INSERT INTO processed_requests (request_id, processed_at) VALUES ($1, NOW())",
        req.ID,
    )
    if err != nil {
        return err
    }
    
    return tx.Commit()
}

幂等性最佳实践 ​

go
// 最佳实践:幂等活动模板
func IdempotentActivityTemplate(ctx context.Context, req Request) error {
    logger := activity.GetLogger(ctx)
    
    // 1. 生成或使用唯一ID
    requestID := req.ID
    if requestID == "" {
        requestID = uuid.New().String()
    }
    
    // 2. 检查幂等性键
    processed, err := isProcessed(requestID)
    if err != nil {
        return fmt.Errorf("failed to check processed status: %w", err)
    }
    if processed {
        logger.Info("Request already processed", "requestID", requestID)
        return nil
    }
    
    // 3. 获取恢复点
    var checkpoint Checkpoint
    if activity.HasHeartbeatDetails(ctx) {
        activity.GetHeartbeatDetails(ctx, &checkpoint)
    }
    
    // 4. 执行业务逻辑(从恢复点开始)
    result, err := processWithCheckpoint(req, checkpoint)
    if err != nil {
        // 记录失败(可选)
        return err
    }
    
    // 5. 标记为已处理
    if err := markAsProcessed(requestID, result); err != nil {
        return fmt.Errorf("failed to mark as processed: %w", err)
    }
    
    return nil
}

Local Activity vs Remote Activity ​

Temporal 提供两种活动类型:本地活动和远程活动。

对比表 ​

特性Local ActivityRemote Activity
执行位置同一 Worker 进程任意 Worker
延迟低(无网络开销)高(有网络开销)
超时限制短(< 1 分钟)长(可数小时)
重试有限完整支持
心跳不支持支持
网络故障无影响可能影响
适用场景快速本地操作外部服务调用

Local Activity ​

go
func WorkflowWithLocalActivity(ctx workflow.Context) error {
    // 配置本地活动选项
    lao := workflow.LocalActivityOptions{
        StartToCloseTimeout: time.Minute,  // 必须较短
    }
    ctx = workflow.WithLocalActivityOptions(ctx, lao)
    
    // 执行本地活动
    var result string
    err := workflow.ExecuteLocalActivity(ctx, LocalActivity, "input").Get(ctx, &result)
    if err != nil {
        return err
    }
    
    return nil
}

// 本地活动定义(与普通活动相同)
func LocalActivity(ctx context.Context, input string) (string, error) {
    // 快速本地操作
    return "processed: " + input, nil
}

Local Activity 适用场景 ​

go
// 适合:本地计算
func ComputeHashActivity(ctx context.Context, data []byte) (string, error) {
    hash := sha256.Sum256(data)
    return hex.EncodeToString(hash[:]), nil
}

// 适合:本地缓存操作
func CacheGetActivity(ctx context.Context, key string) (string, error) {
    return cache.Get(key), nil
}

// 不适合:外部API调用(应该用远程活动)
func CallExternalAPIActivity(ctx context.Context, url string) error {
    resp, err := http.Get(url)  // 网络调用,应该用远程活动
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    return nil
}

Remote Activity ​

go
func WorkflowWithRemoteActivity(ctx workflow.Context) error {
    // 配置远程活动选项
    ao := workflow.ActivityOptions{
        StartToCloseTimeout: 5 * time.Minute,
        HeartbeatTimeout:     30 * time.Second,  // 支持心跳
        RetryPolicy: &temporal.RetryPolicy{      // 完整重试支持
            MaximumAttempts: 5,
        },
    }
    ctx = workflow.WithActivityOptions(ctx, ao)
    
    // 执行远程活动
    var result string
    err := workflow.ExecuteActivity(ctx, RemoteActivity, "input").Get(ctx, &result)
    if err != nil {
        return err
    }
    
    return nil
}

混合使用 ​

go
func MixedWorkflow(ctx workflow.Context, order Order) error {
    // 本地活动:验证订单(快速、本地计算)
    lao := workflow.LocalActivityOptions{
        StartToCloseTimeout: 10 * time.Second,
    }
    localCtx := workflow.WithLocalActivityOptions(ctx, lao)
    
    if err := workflow.ExecuteLocalActivity(localCtx, ValidateOrder, order).Get(ctx, nil); err != nil {
        return err
    }
    
    // 远程活动:处理支付(需要调用外部服务)
    ao := workflow.ActivityOptions{
        StartToCloseTimeout: 5 * time.Minute,
        RetryPolicy: &temporal.RetryPolicy{
            MaximumAttempts: 3,
        },
    }
    remoteCtx := workflow.WithActivityOptions(ctx, ao)
    
    var paymentID string
    if err := workflow.ExecuteActivity(remoteCtx, ProcessPayment, order).Get(ctx, &paymentID); err != nil {
        return err
    }
    
    // 本地活动:更新缓存
    if err := workflow.ExecuteLocalActivity(localCtx, UpdateCache, order.ID, paymentID).Get(ctx, nil); err != nil {
        // 缓存更新失败不影响主流程
        workflow.GetLogger(ctx).Warn("Failed to update cache", "error", err)
    }
    
    return nil
}

Go 代码示例 ​

完整示例:电商订单处理 ​

go
package activity

import (
    "context"
    "fmt"
    "time"
    
    "go.temporal.io/sdk/activity"
    "go.temporal.io/sdk/temporal"
)

// 订单结构
type Order struct {
    ID         string
    CustomerID string
    Items      []OrderItem
    Total      float64
}

type OrderItem struct {
    ProductID string
    Quantity  int
    Price     float64
}

// 库存服务接口
type InventoryService interface {
    CheckAvailability(productID string, quantity int) (bool, error)
    Reserve(productID string, quantity int) error
    Release(productID string, quantity int) error
}

// 支付服务接口
type PaymentService interface {
    Process(orderID string, amount float64) (string, error)
    Refund(transactionID string) error
}

// 物流服务接口
type ShippingService interface {
    Ship(orderID string, address string) (string, error)
}

// 活动结构体
type OrderActivity struct {
    Inventory InventoryService
    Payment   PaymentService
    Shipping  ShippingService
}

// 验证订单活动
func (a *OrderActivity) ValidateOrder(ctx context.Context, order Order) error {
    logger := activity.GetLogger(ctx)
    logger.Info("Validating order", "orderID", order.ID)
    
    // 验证订单项
    if len(order.Items) == 0 {
        return temporal.NewNonRetryableApplicationError(
            "order has no items",
            "InvalidOrder",
            nil,
        )
    }
    
    // 验证总额
    var total float64
    for _, item := range order.Items {
        total += item.Price * float64(item.Quantity)
    }
    if total != order.Total {
        return fmt.Errorf("order total mismatch: expected %.2f, got %.2f", total, order.Total)
    }
    
    return nil
}

// 检查库存活动
func (a *OrderActivity) CheckInventory(ctx context.Context, order Order) error {
    logger := activity.GetLogger(ctx)
    
    // 获取恢复进度
    var checked map[string]bool
    if activity.HasHeartbeatDetails(ctx) {
        activity.GetHeartbeatDetails(ctx, &checked)
    } else {
        checked = make(map[string]bool)
    }
    
    // 检查每个商品
    for _, item := range order.Items {
        if checked[item.ProductID] {
            continue
        }
        
        available, err := a.Inventory.CheckAvailability(item.ProductID, item.Quantity)
        if err != nil {
            return fmt.Errorf("failed to check inventory for %s: %w", item.ProductID, err)
        }
        if !available {
            return temporal.NewNonRetryableApplicationError(
                fmt.Sprintf("product %s not available", item.ProductID),
                "OutOfStock",
                nil,
            )
        }
        
        checked[item.ProductID] = true
        activity.RecordHeartbeat(ctx, checked)
    }
    
    logger.Info("Inventory checked", "orderID", order.ID)
    return nil
}

// 预留库存活动(幂等)
func (a *OrderActivity) ReserveInventory(ctx context.Context, order Order) error {
    logger := activity.GetLogger(ctx)
    
    // 检查是否已预留
    reservationID := fmt.Sprintf("reserve-%s", order.ID)
    reserved, err := isReserved(reservationID)
    if err != nil {
        return err
    }
    if reserved {
        logger.Info("Inventory already reserved", "orderID", order.ID)
        return nil
    }
    
    // 预留库存
    for _, item := range order.Items {
        if err := a.Inventory.Reserve(item.ProductID, item.Quantity); err != nil {
            // 预留失败,释放已预留的
            for _, i := range order.Items {
                if i.ProductID == item.ProductID {
                    break
                }
                a.Inventory.Release(i.ProductID, i.Quantity)
            }
            return fmt.Errorf("failed to reserve %s: %w", item.ProductID, err)
        }
    }
    
    // 标记为已预留
    if err := markAsReserved(reservationID); err != nil {
        return err
    }
    
    logger.Info("Inventory reserved", "orderID", order.ID)
    return nil
}

// 处理支付活动(幂等)
func (a *OrderActivity) ProcessPayment(ctx context.Context, order Order) (string, error) {
    logger := activity.GetLogger(ctx)
    
    // 检查是否已支付
    paymentID, paid, err := getPaymentStatus(order.ID)
    if err != nil {
        return "", err
    }
    if paid {
        logger.Info("Payment already processed", "orderID", order.ID, "paymentID", paymentID)
        return paymentID, nil
    }
    
    // 处理支付
    transactionID, err := a.Payment.Process(order.ID, order.Total)
    if err != nil {
        return "", fmt.Errorf("payment failed: %w", err)
    }
    
    // 保存支付状态
    if err := savePaymentStatus(order.ID, transactionID); err != nil {
        return "", err
    }
    
    logger.Info("Payment processed", "orderID", order.ID, "transactionID", transactionID)
    return transactionID, nil
}

// 发货活动
func (a *OrderActivity) ShipOrder(ctx context.Context, order Order, address string) (string, error) {
    logger := activity.GetLogger(ctx)
    
    // 发送心跳
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
    
    done := make(chan string, 1)
    errCh := make(chan error, 1)
    
    go func() {
        trackingNumber, err := a.Shipping.Ship(order.ID, address)
        if err != nil {
            errCh <- err
            return
        }
        done <- trackingNumber
    }()
    
    for {
        select {
        case trackingNumber := <-done:
            logger.Info("Order shipped", "orderID", order.ID, "trackingNumber", trackingNumber)
            return trackingNumber, nil
            
        case err := <-errCh:
            return "", fmt.Errorf("shipping failed: %w", err)
            
        case <-ticker.C:
            // 发送心跳
            activity.RecordHeartbeat(ctx, "shipping in progress")
            
        case <-ctx.Done():
            // 检查取消请求
            if activity.IsCancelRequested(ctx) {
                return "", activity.NewCanceledError()
            }
        }
    }
}

// 取消订单活动
func (a *OrderActivity) CancelOrder(ctx context.Context, order Order, reason string) error {
    logger := activity.GetLogger(ctx)
    logger.Info("Cancelling order", "orderID", order.ID, "reason", reason)
    
    // 释放库存
    for _, item := range order.Items {
        a.Inventory.Release(item.ProductID, item.Quantity)
    }
    
    // 退款(如果已支付)
    paymentID, paid, _ := getPaymentStatus(order.ID)
    if paid {
        a.Payment.Refund(paymentID)
    }
    
    logger.Info("Order cancelled", "orderID", order.ID)
    return nil
}

// 辅助函数(示例实现)
func isReserved(reservationID string) (bool, error) {
    // 查询数据库
    return false, nil
}

func markAsReserved(reservationID string) error {
    // 更新数据库
    return nil
}

func getPaymentStatus(orderID string) (string, bool, error) {
    // 查询数据库
    return "", false, nil
}

func savePaymentStatus(orderID, transactionID string) error {
    // 更新数据库
    return nil
}

最佳实践 ​

1. 活动设计原则 ​

  • 单一职责:每个活动只做一件事
  • 幂等性:确保重复执行不产生副作用
  • 合理超时:根据实际执行时间设置超时
  • 心跳报告:长时间运行的活动必须发送心跳

2. 错误处理 ​

go
func RobustActivity(ctx context.Context, req Request) error {
    logger := activity.GetLogger(ctx)
    
    // 区分错误类型
    err := doSomething(req)
    if err != nil {
        if isBusinessError(err) {
            // 业务错误:不可重试
            return temporal.NewNonRetryableApplicationError(
                err.Error(),
                "BusinessError",
                nil,
            )
        }
        if isNetworkError(err) {
            // 网络错误:可重试
            return err
        }
        // 其他错误
        return fmt.Errorf("unexpected error: %w", err)
    }
    
    return nil
}

3. 资源管理 ​

go
func ResourceManagementActivity(ctx context.Context, req Request) error {
    // 获取资源
    resource, err := acquireResource()
    if err != nil {
        return err
    }
    
    // 确保释放资源
    defer releaseResource(resource)
    
    // 处理业务逻辑
    return processWithResource(resource, req)
}

4. 日志和监控 ​

go
func ObservabilityActivity(ctx context.Context, req Request) error {
    logger := activity.GetLogger(ctx)
    
    // 记录开始
    startTime := time.Now()
    logger.Info("Activity started", "requestID", req.ID)
    
    // 记录指标
    activity.GetMetricsHandler(ctx).Counter("activity_starts").Inc(1)
    
    // 处理业务逻辑
    err := processRequest(req)
    
    // 记录结果
    duration := time.Since(startTime)
    if err != nil {
        logger.Error("Activity failed", "error", err, "duration", duration)
        activity.GetMetricsHandler(ctx).Counter("activity_failures").Inc(1)
    } else {
        logger.Info("Activity completed", "duration", duration)
        activity.GetMetricsHandler(ctx).Counter("activity_completions").Inc(1)
    }
    
    return err
}

相关资源 ​