

# Lambda 持久性函数的重试次数
<a name="durable-execution-sdk-retries"></a>

持久性函数提供自动重试功能，这使您的应用程序能够抵御暂时性的故障。SDK 在两个层面处理重试：一是针对业务逻辑故障的步骤重试；二是针对基础设施故障的后端重试。

## 步骤重试
<a name="durable-step-retries"></a>

当某个步骤中出现未捕获的异常时，SDK 会根据所配置的重试策略自动重试该步骤。步骤重试是检查点操作，它使 SDK 能够暂停执行并稍后恢复，从而不会丢失进度。

### 步骤重试行为
<a name="durable-step-retry-behavior"></a>

下表介绍了 SDK 如何在步骤中处理异常情况：


| 场景 | 发生了什么 | 计量影响 | 
| --- | --- | --- | 
| 剩余重试次数的步骤中出现异常 | SDK 会为重试创建检查点并暂停该函数。下次调用时，该步骤按照所配置回退延迟重试。 | 1 个操作 \+ 错误有效载荷大小 | 
| 没有剩余重试次数的步骤中出现异常 | 步骤失败并引发异常。如果您的处理程序代码未能捕获此异常，则整个执行过程将会失败。 | 1 个操作 \+ 错误有效载荷大小 | 

当某个步骤需要重试时，SDK 会对重试状态进行检查点处理，并在没有其他工作正在运行的情况下退出 Lambda 调用。这样，SDK 就可以在不消耗计算资源的情况下实施回退延迟。回退期过后，函数将自动恢复。

### 配置步骤重试策略
<a name="durable-step-retry-configuration"></a>

配置重试策略以控制步骤处理失败的方式。您可以指定最大尝试次数、回退间隔和重试条件。

**具有最大尝试次数的指数回退：**

------
#### [ TypeScript ]

```
const result = await context.step('call-api', async () => {
  const response = await fetch('https://api.example.com/data');
  if (!response.ok) throw new Error(`API error: ${response.status}`);
  return await response.json();
}, {
  retryStrategy: (error, attemptCount) => {
    if (attemptCount >= 5) {
      return { shouldRetry: false };
    }
    // Exponential backoff: 2s, 4s, 8s, 16s, 32s (capped at 300s)
    const delay = Math.min(2 * Math.pow(2, attemptCount - 1), 300);
    return { shouldRetry: true, delay: { seconds: delay } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    if attempt_count >= 5:
        return RetryDecision(should_retry=False)
    # Exponential backoff: 2s, 4s, 8s, 16s, 32s (capped at 300s)
    delay = min(2 * (2 ** (attempt_count - 1)), 300)
    return RetryDecision(should_retry=True, delay=delay)

result = context.step(
    lambda _: call_external_api(),
    name='call-api',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**固定间隔回退：**

------
#### [ TypeScript ]

```
const orders = await context.step('query-orders', async () => {
  return await queryDatabase(event.userId);
}, {
  retryStrategy: (error, attemptCount) => {
    if (attemptCount >= 3) {
      return { shouldRetry: false };
    }
    return { shouldRetry: true, delay: { seconds: 5 } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    if attempt_count >= 3:
        return RetryDecision(should_retry=False)
    return RetryDecision(should_retry=True, delay=5)

orders = context.step(
    lambda _: query_database(event['userId']),
    name='query-orders',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**有条件重试（仅重试特定错误）：**

------
#### [ TypeScript ]

```
const result = await context.step('call-rate-limited-api', async () => {
  const response = await fetch('https://api.example.com/data');
  
  if (response.status === 429) throw new Error('RATE_LIMIT');
  if (response.status === 504) throw new Error('TIMEOUT');
  if (!response.ok) throw new Error(`API_ERROR_${response.status}`);
  
  return await response.json();
}, {
  retryStrategy: (error, attemptCount) => {
    // Only retry rate limits and timeouts
    const isRetryable = error.message === 'RATE_LIMIT' || error.message === 'TIMEOUT';
    
    if (!isRetryable || attemptCount >= 3) {
      return { shouldRetry: false };
    }
    
    // Exponential backoff: 1s, 2s, 4s (capped at 30s)
    const delay = Math.min(Math.pow(2, attemptCount - 1), 30);
    return { shouldRetry: true, delay: { seconds: delay } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    # Only retry rate limits and timeouts
    is_retryable = str(error) in ['RATE_LIMIT', 'TIMEOUT']
    
    if not is_retryable or attempt_count >= 3:
        return RetryDecision(should_retry=False)
    
    # Exponential backoff: 1s, 2s, 4s (capped at 30s)
    delay = min(2 ** (attempt_count - 1), 30)
    return RetryDecision(should_retry=True, delay=delay)

result = context.step(
    lambda _: call_rate_limited_api(),
    name='call-rate-limited-api',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**禁用重试：**

------
#### [ TypeScript ]

```
const isDuplicate = await context.step('check-duplicate', async () => {
  return await checkIfOrderExists(event.orderId);
}, {
  retryStrategy: () => ({ shouldRetry: false })
});
```

------
#### [ Python ]

```
is_duplicate = context.step(
    lambda _: check_if_order_exists(event['orderId']),
    name='check-duplicate',
    config=StepConfig(
        retry_strategy=lambda error, attempt: {'should_retry': False}
    )
)
```

------

当重试策略返回 `shouldRetry: false` 时，步骤会立即失败，无需重试。将此用于那些不应被重试的操作，例如幂等性检查或具有副作用且无法安全重复的操作。

## 步骤之外的异常
<a name="durable-handler-exceptions"></a>

当在处理程序代码中出现未捕获的异常，且该异常未发生在任何步骤之内时，SDK 会将此次执行标记为失败。这能确保您应用程序逻辑中的错误能够被准确地捕获并报告出来。


| 场景 | 发生了什么 | 计量影响 | 
| --- | --- | --- | 
| 在任何步骤之外的处理程序代码中出现异常 | SDK 将执行标记为 FAILED 并返回错误。异常不会被自动重试。 | 错误有效载荷大小 | 

为了使易出错的代码能够自动重试，可以将该代码封装在一个带有重试策略的步骤中。步骤提供自动重试和可配置的回退功能，而步骤之外的代码会立即失败。

## 调用重试
<a name="durable-invocation-retries"></a>

调用级别的重试处理机制，因 Lambda 持久性函数的调用方式而异。下表描述了不同调用类型可能对调用级别重试的影响。


| 调用类型 | 发生了什么 | 
| --- | --- | 
| 同步调用 |  在持久性函数执行期间，如果出现错误，Lambda 不会自动重试调用。调用失败时的重试次数取决于同步调用的来源。例如，使用 AWS SDK，默认情况下会自动重试 InternalFailure 和 ThrottlingException。 | 
| 异步调用 |  如果持久性函数执行失败（例如进入 FAILED、STOPPED 或 TIMED\_OUT 状态），则 Lambda 不会重试执行。这与标准 Lambda 函数不同：当异步调用失败时，Lambda 会自动重试标准 Lambda 函数。异步调用的 MaximumRetryAttempts 设置不适用于持久执行。如果为函数配置了死信队列（DLQ），Lambda 会将触发事件发送到 DLQ。 | 
| 事件源映射（ESM） |  默认情况下，Lambda 会持续重试整批任务，直至执行成功。对于流源（DynamoDB 和 Kinesis），可以配置 Lambda 在函数返回错误时重试的最大次数。请参阅[事件源映射批处理](invocation-eventsourcemapping.md#invocation-eventsourcemapping-batching)。对于 Amazon SQS ESM，可通过原始 Amazon SQS 队列绑定的 DLQ 配置最大重试次数。请参阅[配置 Amazon SQS ESM](services-sqs-configure.md)。此外，亦可在函数级别配置 DLQ，由 Lambda 将触发失败的事件转发到该 DLQ。请参阅[函数 DLQ](invocation-async-retain-records.md#invocation-dlq)。若需若需获取全部处理尝试失败或成功的事件记录，可为 ESM 配置目标。请参阅[异步调用目标](invocation-async-retain-records.md#invocation-async-destinations)。 | 
| 直接触发器 |  由触发器类型决定。例如，Lambda 会异步处理由 Amazon S3 事件通知触发的函数。请参阅[使用 Lambda 处理 Amazon SQS 事件通知](with-sqs.md)。Lambda 会异步处理由 Amazon SNS 事件通知触发的函数。请参阅[使用 Amazon SNS 通知调用 Lambda 函数](with-sns.md)。异步调用的重试行为，详见上方“异步调用”表格。如果无法联系到 Lambda 或消息被拒绝，Amazon SNS 将在几个小时内以递增的间隔重试。有关详细信息，请参阅 Amazon SNS 常见问题中的[可靠性](https://aws.amazon.com/sns/faqs/#Reliability)。API Gateway 会同步调用 Lambda，并将原始错误响应返回给请求者。请参阅[调用重试](invocation-retries.md)。同步调用的重试行为，详见上方“同步调用”表格。有关更多详细信息，请参阅[各个直接触发器](invocation-eventsourcemapping.md#eventsourcemapping-trigger-difference)。 | 

## 后端重试
<a name="durable-backend-retries"></a>

当 Lambda 遇到基础设施故障、运行时错误或者 SDK 无法与持久执行服务进行通信时，就会出现后端重试。Lambda 会自动重试这些故障，以帮助持久性函数能够从暂时基础设施问题中恢复。

### 后端重试场景
<a name="durable-backend-retry-scenarios"></a>

当 Lambda 遇到以下情况时，会自动重试您的函数：
+ **内部服务错误**：当 Lambda 或持久执行服务返回 5xx 错误时，表示存在暂时性的服务问题。
+ **节流**：当您的函数因并发限制或服务配额而被限制时。
+ **超时**：当 SDK 在超时时间内无法连接到持久执行服务时。
+ **沙盒初始化失败**：当 Lambda 无法初始化执行环境时。
+ **运行时错误**：当 Lambda 运行时遇到超出您函数代码范围的错误时，例如内存不足错误或进程崩溃等。
+ **无效的检查点令牌错误**：当检查点令牌不再有效时，通常是因为服务端状态发生变化所致。

下表描述了 SDK 如何处理这些场景：


| 场景 | 发生了什么 | 计量影响 | 
| --- | --- | --- | 
| 持久性处理程序之外的运行时错误（OOM、超时、崩溃） | Lambda 会自动重试调用。SDK 会从上一个检查点开始重放，以跳过已完成的步骤。 | 错误有效载荷大小 \+ 每次重试 1 次操作 | 
| 调用 CheckpointDurableExecution / GetDurableExecutionState API 时出现服务错误（5xx）或超时 | Lambda 会自动重试调用。SDK 会从上一个检查点开始重放。 | 错误有效载荷大小 \+ 每次重试 1 次操作 | 
| 调用 CheckpointDurableExecution / GetDurableExecutionState API时出现节流（429）或无效的检查点令牌 | Lambda 会自动通过指数回退重试调用。SDK 会从上一个检查点开始重放。 | 错误有效载荷大小 \+ 每次重试 1 次操作 | 
| 调用 CheckpointDurableExecution / GetDurableExecutionState API 时出现客户端错误（4xx，429 和无效令牌除外） | SDK 将执行标记为 FAILED。由于该错误表明存在永久性问题，所以不会自动重试。 | 错误有效载荷大小 | 

后端重试采用指数回退策略，并持续进行直至函数成功执行或达到执行超时时间。在重放过程中，SDK 跳过已完成的检查点，并从上一次成功的操作处继续执行，从而确保您的函数不会重复执行已完成的工作。

## 重试最佳实践
<a name="durable-retry-best-practices"></a>

在配置重试策略时，请遵循以下最佳实践：
+ **配置明确的重试策略**：不要依赖生产环境中的默认重试行为。为您的使用案例配置明确的重试策略，包括适当的最大重试次数和回退间隔。
+ **使用条件重试**：实施 `shouldRetry` 逻辑，以仅对暂时出现的错误（如速率限制、超时）进行重试，而对于永久性的错误（如验证失败、未找到）则启动快速失效机制。
+ **设置适当的最大尝试次数**：在韧性与执行时间之间取得平衡。尝试次数过多会延缓故障的检测，而尝试次数过少则可能导致不必要的故障发生。
+ **使用指数回退**：指数回退能够减轻下游服务的负载，并提高从暂时性故障中恢复的可能性。
+ **在步骤中封装容易出错的代码**：步骤之外的代码无法自动重试。将外部 API 调用、数据库查询以及其他容易出错的操作封装在具有重试策略的步骤中。
+ **监控重试指标**：在 Amazon CloudWatch 中跟踪步骤重试操作及执行失败情况，以识别模式并优化重试策略。