

# 幂等性
<a name="durable-execution-idempotency"></a>

持久性函数通过执行名称为执行启动提供内置的幂等性。当您提供执行名称时，Lambda 会利用该名称来防止重复执行，并确保调用请求能够安全地进行重试。步骤默认具有至少一次执行的语义——在回放过程中，SDK 会返回已检查点保存的结果，而不会重新执行已完成的步骤，但您的业务逻辑必须是幂等性的，以便在完成之前处理可能的重试情况。

**注意**  
Lambda 事件源映射（ESM）在启动时不支持幂等性。因此，每次调用（包括重试）都会启动一个新的持久执行。为确保事件源映射下的执行具有幂等性，可以在您的函数代码中实施幂等性逻辑，例如使用 [Powertools for AWS Lambda](https://docs.aws.amazon.com//powertools/) 来实施，或者使用常规的 Lambda 函数作为代理（调度程序）来调用具有幂等键（执行名称参数）的持久性函数。

## 执行名称
<a name="durable-idempotency-execution-names"></a>

在调用持久性函数时，您可以提供一个执行名称。执行名称充当幂等键，使您能够安全地重试调用请求，而无需创建重复的执行。如果您不提供名称，Lambda 会自动生成一个唯一的执行 ID。

执行名称必须在您的账户和区域内唯一。当您使用已存在的执行名称调用函数时，Lambda 的行为将取决于现有执行的状态以及有效载荷是否与之匹配。

## 幂等性行为
<a name="durable-idempotency-behavior"></a>

以下表格说明了 Lambda 如何根据您是否提供了执行名称、现有执行状态以及有效载荷是否匹配等因素来处理调用请求：


| 场景 | 是否提供了名称？ | 现有执行状态 | 有效载荷相同？ | 行为 | 
| --- | --- | --- | --- | --- | 
| 1 | 否 | 不适用 | 不适用 | 新执行已开始：Lambda 生成一个唯一的执行 ID 并开始新的执行 | 
| 2 | 可以 | 从未存在或保留期已过期 | 不适用 | 新执行已开始：Lambda 使用提供的名称开始新执行 | 
| 3 | 是 | 运行 | 是 | 幂等性启动：Lambda 返回现有的执行信息，而不会启动重复的执行过程。对于同步调用而言，这相当于是重新附加到正在运行的执行 | 
| 4 | 是 | 运行 | 否 | 错误：Lambda 返回 DurableExecutionAlreadyExists 错误，因为具有此名称的执行已经在运行，且具有不同的有效载荷 | 
| 5 | 是 | 已关闭（成功、失败、停止或超时） | 是 | 幂等性启动：Lambda 返回现有的执行信息，而不会启动新执行。将返回已关闭执行的结果 | 
| 6 | 是 | 已关闭（成功、失败、停止或超时） | 否 | 错误：Lambda 返回 DurableExecutionAlreadyExists 错误，因为具有此名称的执行已经在运行，且具有不同的有效载荷 | 

**注意**  
场景 3 和方案 5 展示了幂等性行为，其中 Lambda 能够安全地处理重复的调用请求，它会返回现有的执行信息，而非创建重复项。

## 步骤幂等性
<a name="durable-idempotency-steps"></a>

步骤默认具有至少一次的执行语义。当您的函数在等待、回调或出现故障后重放时，SDK 会将每个步骤与检查点日志进行比对。对于已经完成的步骤，SDK 会返回已进行检查点保存的结果，但不会重新执行该步骤逻辑。但是，如果某个步骤失败或者在该步骤完成之前函数被中断，则该步骤可能会执行多次。

封装在步骤中的业务逻辑必须是幂等性的，以便能够处理可能出现的重试情况。使用幂等键来确保诸如支付或数据库写入等操作仅执行一次，即便该步骤出现重试情况也是如此。

**示例：在步骤中使用幂等键**

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

```
import { withDurableExecution, DurableContext } from '@aws/durable-execution-sdk-js';
import { randomUUID } from 'crypto';

export const handler = withDurableExecution(
  async (event: any, context: DurableContext) => {
    // Generate idempotency key once
    const idempotencyKey = await context.step('generate-key', async () => {
      return randomUUID();
    });
    
    // Use idempotency key in payment API to prevent duplicate charges
    const payment = await context.step('process-payment', async () => {
      return paymentAPI.charge({
        amount: event.amount,
        idempotencyKey: idempotencyKey
      });
    });
    
    return { statusCode: 200, payment };
  }
);
```

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

```
from aws_durable_execution_sdk_python import durable_execution, DurableContext
import uuid

@durable_execution
def handler(event, context: DurableContext):
    # Generate idempotency key once
    idempotency_key = context.step(
        lambda _: str(uuid.uuid4()),
        name='generate-key'
    )
    
    # Use idempotency key in payment API to prevent duplicate charges
    payment = context.step(
        lambda _: payment_api.charge(
            amount=event['amount'],
            idempotency_key=idempotency_key
        ),
        name='process-payment'
    )
    
    return {'statusCode': 200, 'payment': payment}
```

------

您可以通过将执行模式设置为 `AT_MOST_ONCE_PER_RETRY` 来配置采用最多一次执行语义的步骤。这确保了该步骤在每次重试尝试中最多执行一次，但如果在步骤完成之前函数被中断，则该步骤可能根本不会执行。

SDK 通过验证该步骤名称和订单是否与回放期间的检查点日志相匹配，来实施确定性重放。如果您的代码试图按照不同的顺序或使用不同的名称来执行这些步骤，则 SDK 会抛出 `NonDeterministicExecutionError`。

**重放如何与完成的步骤结合使用：**

1. 第一次调用：函数执行步骤 A，创建检查点，然后等待

1. 第二次调用（等待后）：函数从头开始重放，步骤 A 立即返回检查点结果，但不会重新执行，然后继续执行步骤 B

1. 第二次调用（等待后）：函数从头开始重放，步骤 A 和 B 立即返回检查点结果，然后继续执行步骤 C

这种回放机制确保已完成的步骤不会再次执行，但您的业务逻辑仍必须是“幂等性”的，以便在完成之前处理重试情况。