

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# Step Functions 的最佳实操
<a name="sfn-best-practices"></a>

**管理状态和转换数据**  
了解有关[使用变量在状态之间传递数据](workflow-variables.md)和[使用转换数据](transforming-data.md)的信息 JSONata。

以下主题是有助于您管理和优化 Step Functions 工作流程的最佳实践。

**Topics**
+ [使用快速工作流程优化成本](#cost-opt-exp-workflows)
+ [在 Step Functions 中标记状态机和活动](#concepts-tagging)
+ [使用超时来避免 Step Functions 工作流程执行卡顿](#sfn-stuck-execution)
+ [使用 Amazon S3 ARNs 而不是在 Step Functions 中传递大型有效负载](#avoid-exec-failures)
+ [在 Step Functions 中启动新的执行以避免达到历史记录配额](#bp-history-limit)
+ [处理短暂的 Lambda 服务异常](#bp-lambda-serviceexception)
+ [避免轮询活动任务时发生延迟](#bp-activity-pollers)
+ [避免 CloudWatch 资源策略大小限制](#bp-cwl)

## 使用快速工作流程优化成本
<a name="cost-opt-exp-workflows"></a>

Step Functions 根据您用来构建状态机的工作流类型来确定标准和快速工作流的定价。要优化无服务器工作流的成本，您可以遵循以下一项或两项建议：

有关选择标准或快速工作流类型如何影响账单的信息，请参阅[Amazon Step Functions 定价](https://www.amazonaws.cn/step-functions/pricing/)。

### 在标准工作流程中嵌套快速工作流程
<a name="cost-opt-exp-wflow-nesting"></a>

Step Functions 运行的工作流具有有限的持续时间和步骤数量。有些工作流可能会在短时间内完成执行。其他可能需要将长期运行和 high-event-rate工作流程结合起来。借助 Step Functions，您可以利用多个更小、更简单的工作流来构建大型复杂的工作流。

例如，要构建订单处理工作流，您可以将所有非幂等操作包含到标准工作流中。这可能包括通过人际互动批准订单和处理付款等操作。然后，您可以在快速工作流中组合一系列幂等操作，例如发送付款通知和更新产品库存。您可以将此快速工作流嵌套在标准工作流中。在此示例中，标准工作流被称为*父状态机*。嵌套的快速工作流被称为*子状态机*。

### 将标准工作流迁移到快速工作流
<a name="cost-opt-exp-wflow-conversion"></a>

如果现有标准工作流满足以下要求，您应考虑可以将其迁移到快速工作流：
+ 工作流必须在五分钟内完成执行。
+ 您的工作流程符合*at-least-once*执行模型，这意味着工作流程中的每个步骤都可能运行多次。
+ 工作流**不**使用 `.waitForTaskToken` 或 `.sync` 服务集成模式。

**重要**  
Express 工作流程使用 Amazon CloudWatch 日志记录执行历史记录。使用 CloudWatch 日志将产生额外费用。

**使用控制台将标准工作流转换为快速工作流**

1. 打开 [Step Functions 控制台](https://console.amazonaws.cn/states/home?region=us-east-1#/)。

1. 在**状态机**页面上，选择一个标准类型的状态机将其打开。
**提示**  
从**任意类型**下拉列表中，选择**标准**以筛选状态机列表，并仅查看标准工作流。

1. 选择**复制到新项目**。

   Workflow Studio 在[设计模式](workflow-studio.md#wfs-interface-design-mode)下打开，显示所选状态机的工作流。

1. （可选）更新工作流设计。

1. 为状态机指定一个名称。为此，请选择默认状态机名称旁边的编辑图标**MyStateMachine**。然后，找到**状态机配置**，在**状态机名称**框中指定一个名称。

1. （可选）在**状态机配置**中，指定其他工作流设置，例如状态机类型及其执行角色。

   确保在**类型**中选择**快速**。保留**状态机设置**中的所有其他默认选项。
**注意**  
如果您要迁移之前在[Amazon CDK](https://docs.amazonaws.cn/cdk/api/latest/docs/aws-stepfunctions-readme.html)或中定义的标准工作流程Amazon SAM，则必须更改`Type`和`Resource`名称的值。

1. 在**确认角色创建**对话框中，选择**确认**继续。

   您也可以选择**查看角色设置**，返回至**状态机配置**。
**注意**  
如果您删除了 Step Functions 创建的 IAM 角色，Step Functions 在以后无法重新创建该角色。同样，如果您修改了该角色（例如，通过在 IAM 策略中从主体中删除 Step Functions），Step Functions 在以后也无法还原其原始设置。

有关管理工作流程成本优化时的最佳实践和指南的更多信息，请参阅[构建经济高效Amazon Step Functions的工作流程](https://www.amazonaws.cn/blogs/compute/building-cost-effective-aws-step-functions-workflows/)。

## 在 Step Functions 中标记状态机和活动
<a name="concepts-tagging"></a>

Amazon Step Functions支持标记状态机（标准和快速）和活动。标签可以帮助您跟踪和管理资源，并提高您的 Amazon Identity and Access Management (IAM) 策略的安全性。标记 Step Functions 资源后，你可以使用对其进行Amazon Resource Groups管理。要了解如何操作，请参阅 [Amazon Resource Groups User Guide](https://docs.amazonaws.cn/ARG/latest/userguide/)。

对于基于标签的授权，状态机执行资源（如下例所示）会继承与状态机关联的标签。

```
arn:partition:states:region:account-id:execution:<StateMachineName>:<ExecutionId>
```

当您调用[DescribeExecution](https://docs.amazonaws.cn/step-functions/latest/apireference/API_DescribeExecution.html)或 APIs 以其他方式指定执行资源 ARN 时，Step Functions 会在执行基于标签的授权时使用与状态机关联的标签来接受或拒绝请求。这有助于在状态机级别允许或拒绝对状态机执行的访问。

要查看与资源标记相关的限制，请参阅[与标记相关的限制](service-quotas.md#sfn-limits-tagging)。

### 成本分配的标记
<a name="tagging-cost"></a>

您可以使用成本分配标签来识别状态机的用途，并在Amazon账单中反映该组织。注册以获取包含标签密钥和值的Amazon账户账单。有关设置报告的详细信息，请参阅《Amazon Billing用户指南》**中的 [Setting Up a Monthly Cost Allocation Report](https://docs.amazonaws.cn/awsaccountbilling/latest/aboutv2/configurecostallocreport.html#allocation-report)。

例如，可以添加表示 Step Functions 资源的成本中心和用途的标签，如下所示。

[\[See the AWS documentation website for more details\]](http://docs.amazonaws.cn/step-functions/latest/dg/sfn-best-practices.html)

### 标记以提高安全性
<a name="tagging-security"></a>

IAM 支持基于标签控制对资源的访问。要基于标签控制访问，请在 IAM 策略的条件元素中提供有关您的资源标签的信息。

例如，您可能会限制对下面这样的所有 Step Functions 资源的访问：在这些资源包含的标签中，具有键 `environment` 和值 `production`。

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Deny",
            "Action": [
                "states:TagResource",
                "states:DeleteActivity",
                "states:DeleteStateMachine",
                "states:StopExecution"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {"aws:ResourceTag/environment": "production"}
            }
        }
    ]
}
```

有关更多信息，请参阅《IAM 用户指南》中的[使用标签控制访问](https://docs.amazonaws.cn/IAM/latest/UserGuide/access_tags.html)。

### 在 Step Functions 控制台中管理标签
<a name="tagging-console"></a>

可以在 Step Functions 控制台中查看和管理状态机的标签。在状态机的 **Details (详细信息)** 页面中，选择 **Tags (标签)**。

### 使用 Step Functions API 操作管理标签
<a name="tagging-api"></a>

要使用 Step Functions API 管理标签，请使用以下 API 操作：
+ [https://docs.amazonaws.cn/step-functions/latest/apireference/API_ListTagsForResource.html](https://docs.amazonaws.cn/step-functions/latest/apireference/API_ListTagsForResource.html)
+ [https://docs.amazonaws.cn/step-functions/latest/apireference/API_TagResource.html](https://docs.amazonaws.cn/step-functions/latest/apireference/API_TagResource.html)
+ [https://docs.amazonaws.cn/step-functions/latest/apireference/API_UntagResource.html](https://docs.amazonaws.cn/step-functions/latest/apireference/API_UntagResource.html)

## 使用超时来避免 Step Functions 工作流程执行卡顿
<a name="sfn-stuck-execution"></a>

默认情况下，Amazon States Language 不会为状态机定义指定超时。如果没有显式超时，Step Functions 通常仅依靠来自活动工作线程的响应来了解任务是否已完成。如果发生错误并且 `Activity` 或 `Task` 状态未指定 `TimeoutSeconds` 字段，则执行会卡住，等待永远不会出现的响应。

为避免这种情况，请在状态机中创建 `Task` 时指定合理的超时。例如：

```
"ActivityState": {
  "Type": "Task",
  "Resource": "arn:aws:states:region:account-id:activity:HelloWorld",
  "TimeoutSeconds": 300,
  "Next": "NextState"
}
```

如果您使用[带有任务令牌的回调 (. waitForTaskToken）](connect-to-resource.md#connect-wait-token)，我们建议您使用心跳并在`Task`状态定义中添加该`HeartbeatSeconds`字段。您可以将 `HeartbeatSeconds` 设置为小于任务超时时间，因此，如果您的工作流因检测信号错误而失败，那么您就知道这是因为任务失败，而不是任务需要很长时间才能完成。

```
{
  "StartAt": "Push to SQS",
  "States": {
    "Push to SQS": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
      "HeartbeatSeconds": 600,
      "Parameters": {
        "MessageBody": { "myTaskToken.$": "$$.Task.Token" },
        "QueueUrl": "https://sqs.us-east-1.amazonaws.com/account-id/push-based-queue"
      },
      "ResultPath": "$.SQS",
      "End": true
    }
  }
}
```

有关更多信息，请参阅 Amazon States Language 文档中的 [Task 工作流程状态](state-task.md)。

**注意**  
您可以使用 Amazon States Language 定义中的 `TimeoutSeconds` 字段为状态机设置超时。有关更多信息，请参阅 [Amazon States Language 中用于 Step Functions 工作流程的状态机结构](statemachine-structure.md)。

## 使用 Amazon S3 ARNs 而不是在 Step Functions 中传递大型有效负载
<a name="avoid-exec-failures"></a>

可以终止在状态间传递大量数据负载的执行。如果您在各状态之间传递的数据可能增长到 256 KiB 以上，请使用 Amazon Simple Storage Service（Amazon S3）存储数据，并在 `Payload` 参数中解析存储桶的 Amazon 资源名称（ARN），获取存储桶名称和键值。或者，您也可以调整实现，以便在执行中传递较少的负载。

在以下示例中，状态机将输入传递给一个Amazon Lambda函数，该函数处理 Amazon S3 存储桶中的 JSON 文件。运行此状态机后，Lambda 函数读取 JSON 文件的内容，并将文件内容作为输出返回。

**创建 Lambda 函数**  
以下名为 `pass-large-payload` 的 Lambda 函数读取存储在特定 Amazon S3 存储桶中的 JSON 文件的内容。

**注意**  
创建此 Lambda 函数后，请务必为其 IAM 角色提供相应的权限，使其能够从 Amazon S3 存储桶中读取。例如，将 **AmazonS3 ReadOnlyAccess** 权限附加到 Lambda 函数的角色。

```
import json
import boto3
import io
import os

s3 = boto3.client('s3')

def lambda_handler(event, context):
    event = event['Input']
    final_json = str()
    
    s3 = boto3.resource('s3')
    bucket = event['bucket'].split(':')[-1]
    filename = event['key']
    directory = "/tmp/{}".format(filename)
    
    s3.Bucket(bucket).download_file(filename, directory)
    
    with open(directory, "r") as jsonfile:
    
        final_json = json.load(jsonfile)
    
    os.popen("rm -rf /tmp")
    
    return final_json
```

**创建状态机**  
以下状态机调用您之前创建的 Lambda 函数。

```
{  
   "StartAt":"Invoke Lambda function",
   "States":{  
      "Invoke Lambda function":{  
         "Type":"Task",
         "Resource":"arn:aws:states:::lambda:invoke",
         "Parameters":{  
            "FunctionName":"arn:aws:lambda:us-east-2:123456789012:function:pass-large-payload",
            "Payload":{  
               "Input.$":"$"
            }
         },
         "OutputPath": "$.Payload",
         "End":true
      }
   }
}
```

无需传递输入中的大量数据，您可以将数据存储到一个 Amazon S3 存储桶，然后在 `Payload` 参数中传递该存储桶的 Amazon 资源名称 (ARN)，获取存储桶的名称和密钥值。之后，您的 Lambda 函数就可以使用该 ARN 直接访问数据。以下是状态机执行的输入示例，其中数据以 `amzn-s3-demo-large-payload-json` 形式存储在名为 `data.json` 的 Amazon S3 存储桶中。

```
{
  "key": "data.json",
  "bucket": "arn:aws:s3:::amzn-s3-demo-large-payload-json"
}
```

## 在 Step Functions 中启动新的执行以避免达到历史记录配额
<a name="bp-history-limit"></a>

Amazon Step Functions在执行事件历史记录中有 25,000 个条目的硬配额。当执行达到 24,999 个事件时，它会等待下一个事件发生。
+ 如果事件编号为 25,000 是 `ExecutionSucceeded`，则执行成功完成。
+ 如果事件编号 25,000 不是 `ExecutionSucceeded`，则会记录 `ExecutionFailed` 事件，状态机执行因达到历史记录上限而失败

为了避免长时间运行的执行达到此配额，您可以尝试以下一种变通方法：
+ [使用分布式模式下的 Map 状态](state-map-distributed.md)。在此模式下，`Map` 状态将每次迭代作为子工作流执行运行，从而实现多达 1 万个并行子工作流执行的高并发数。每个子工作流执行都有自己的、独立于父工作流的执行历史记录。
+ 直接从正在执行的 `Task` 状态启动新的状态机执行。要启动此类嵌套工作流执行，请在父状态机中使用 Step Functions 的 `[StartExecution](https://docs.amazonaws.cn/step-functions/latest/apireference/API_StartExecution.html)` API 操作并设置必要的参数。有关使用嵌套工作流的更多信息，请参阅[在 Step Functions 中从 Task 状态启动工作流程执行](concepts-nested-workflows.md)或[使用 Step Functions API 操作继续新的执行](tutorial-continue-new.md)教程。
**提示**  
要部署嵌套工作流程示例，请参阅*Amazon Step Functions研讨会中的*[优化成本](https://catalog.workshops.aws/stepfunctions/nested-workflow)。
+ 实现一种模式，该模式使用可以启动状态机的新执行的Amazon Lambda函数，将正在进行的工作分散到多个工作流程执行中。有关更多信息，请参阅[在 Step Functions 中使用 Lambda 函数继续新的执行](tutorial-use-lambda-cont-exec.md)教程。

## 处理短暂的 Lambda 服务异常
<a name="bp-lambda-serviceexception"></a>

Amazon Lambda偶尔会遇到暂时的服务错误。在这种情况下，调用 Lambda 会导致 500 错误，例如 `ClientExecutionTimeoutException`、`ServiceException`、`AWSLambdaException` 或 `SdkClientException`。作为最佳实操，在状态机中主动处理这些异常，以 `Retry` 调用 Lambda 函数或 `Catch` 错误。

Lambda 错误报告为 `Lambda.ErrorName`。要重试 Lambda 服务异常错误，可以使用以下 `Retry` 代码。

```
"Retry": [ {
   "ErrorEquals": [ "Lambda.ClientExecutionTimeoutException", "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException"],
   "IntervalSeconds": 2,
   "MaxAttempts": 6,
   "BackoffRate": 2
} ]
```

**注意**  
Lambda 运行时中未处理的错误历来仅报告为 `Lambda.Unknown`。在较新的运行时中，超时在错误输出中报告为 `Sandbox.Timedout`。  
当 Lambda 超过最大调用次数时，报告的错误将是 `Lambda.TooManyRequestsException`。  
请匹配 `Lambda.Unknown`、`Sandbox.Timedout` 和 `States.TaskFailed` 这几种错误类型，以处理可能出现的错误。您也可以使用 `States.ALL`，但必须单独使用，并且位于列表的末尾。  
有关 Lambda `Handled` 和 `Unhandled` 错误的更多信息，请参阅 [Amazon Lambda 开发人员指南](https://docs.amazonaws.cn/lambda/latest/dg/API_Invoke.html#API_Invoke_ResponseSyntax)中的 `FunctionError`。

有关更多信息，请参阅下列内容：
+ [出错后重试](concepts-error-handling.md#error-handling-retrying-after-an-error)
+ [在 Step Functions 状态机中处理错误条件](tutorial-handling-error-conditions.md)
+ [Lambda 调用错误](https://docs.amazonaws.cn/lambda/latest/dg/API_Invoke.html#API_Invoke_Errors)

## 避免轮询活动任务时发生延迟
<a name="bp-activity-pollers"></a>

`[GetActivityTask](https://docs.amazonaws.cn/step-functions/latest/apireference/API_GetActivityTask.html)` API 旨在*仅提供一次* [https://docs.amazonaws.cn/step-functions/latest/apireference/API_GetActivityTask.html#StepFunctions-GetActivityTask-response-taskToken](https://docs.amazonaws.cn/step-functions/latest/apireference/API_GetActivityTask.html#StepFunctions-GetActivityTask-response-taskToken)。如果在通过活动工作线程通信时 `taskToken` 被丢弃，大量 `GetActivityTask` 请求可能会被阻止 60 秒以等待响应，直至 `GetActivityTask` 超时。

如果只有少量轮询等待响应，则可能所有请求都会排在被阻止的请求之后，无法处理。但是，如果每个活动 Amazon 资源名称 (ARN) 都有大量未完成轮询，某个百分比的请求需要等待，不过更多的请求仍然可以获取 `taskToken` 并开始处理工作。

对于生产系统，建议每个活动 ARN 在每个时间点至少有 100 个轮询。如果一个轮询被阻止，其后有一部分轮询排队，在 `GetActivityTask` 请求被阻止时，仍然有更多请求将获得 `taskToken` 可以处理工作。

避免在轮询任务时出现这类延迟问题：
+ 在活动工作线程实现的工作之外通过单独的线程实现轮询器。
+ 每个活动 ARN 在每个时间点至少有 100 个轮询。
**注意**  
每个 ARN 都扩展到 100 个轮询会比较昂贵。例如，每个 ARN 100 个 Lambda 函数轮询比具有 100 个轮询线程的单个 Lambda 函数贵 100 倍。要降低延迟*并* 最大限度减少成本，请使用具有异步 I/O 的语言，并且每个工作线程实施多个轮询线程。有关轮询器线程独立于工作线程的示例活动工作线程，请参阅[示例：Ruby 中的活动工作线程](concepts-activities.md#example-ruby-activity-worker)。

有关活动和活动工作线程的更多信息，请参阅[了解 Step Functions 中的活动](concepts-activities.md)。

## 避免 CloudWatch 资源策略大小限制
<a name="bp-cwl"></a>

当您创建带有日志记录的状态机或更新现有状态机以启用日志记录时，Step Function CloudWatch s 必须使用您指定的日志组更新您的日志资源策略。 CloudWatch 日志资源策略限制为 5,120 个字符。

当 CloudWatch Logs 检测到策略接近大小限制时，Lo CloudWatch gs 会自动为以开头的日志组启用日志记录`/aws/vendedlogs/`。

您可以在 CloudWatch 日志日志组名称前加上前缀`/aws/vendedlogs/`，以避免 CloudWatch 日志资源策略的大小限制。如果您在 Step Functions 控制台中创建日志组，则建议的日志组名称将已经以 `/aws/vendedlogs/states` 为前缀。

CloudWatch 对于每个账户，每个区域，日志还有十个资源策略的配额。如果您尝试在某个账户的某个区域中已经有十个 CloudWatch 日志资源策略的状态机上启用日志功能，则不会创建或更新状态机。有关日志配额的更多信息，请参阅[CloudWatch 日志配额](https://docs.amazonaws.cn/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html)。

如果您在向 CloudWatch 日志发送日志时遇到问题，请参阅[Troubleshooting state machine logging to CloudWatch Logs](cw-logs.md#troubleshooting-logging-to-cloudwatch)。