使用 Lambda 递归循环检测来防止无限循环 - AWS Lambda

使用 Lambda 递归循环检测来防止无限循环

当您配置 Lambda 函数输出到调用该函数的同一服务或资源时,就可能会创建无限递归循环。例如,Lambda 函数可能会向 Amazon Simple Queue Service(Amazon SQS)队列写入一条消息,该队列随即调用同一函数。此调用导致该函数向队列写入另一条消息,而队列反过来再次调用该函数。

意外发生的递归循环可能会让您的 AWS 账户 产生意外费用。循环还可能导致 Lambda 扩展并使用您账户的所有可用并发。为了帮助减轻意外循环的影响,Lambda 可以在某些类型的递归循环发生后不久将其检测出。默认情况下,在检测到递归循环时,Lambda 会停止调用函数并向您发送通知。如果您的设计有意使用递归模式,则可以更改函数的默认配置,以允许其以递归方式调用。请参阅允许 Lambda 函数在递归循环中运行了解更多信息。

了解递归循环检测

Lambda 中的递归循环检测通过跟踪事件来工作。Lambda 是一种事件驱动型计算服务,可在某些事件发生时运行您的函数代码。例如,将项目添加到 Amazon SQS 队列或 Amazon Simple Notification Service(Amazon SNS)主题时,就会如此。Lambda 将事件作为 JSON 对象传递给您的函数,其中包含有关系统状态变化的信息。如果某一事件导致您的函数运行时,这就称为调用

为了检测递归循环,Lambda 会使用 AWS X-Ray 跟踪标头。当支持递归循环检测的 AWS 服务 将事件发送到 Lambda 时,这些事件将自动使用元数据进行注释。当您的 Lambda 函数使用支持的 AWS 开发工具包版本将其中一个事件写入另一个支持的 AWS 服务 时,就会更新此元数据。更新后的元数据包括事件调用该函数的次数计数。

注意

您无需启用 X-Ray 主动追踪,即可使用此功能。默认情况下,所有 AWS 客户都启用递归循环检测。使用该功能不会产生任何费用。

请求链是由同一触发事件引起的一系列 Lambda 调用。例如,假设 Amazon SQS 队列调用了您的 Lambda 函数。然后,Lambda 函数将处理过的事件发送回同一 Amazon SQS 队列,该队列将再次调用您的函数。在此示例中,函数的每次调用都属于同一请求链。

如果您的函数在同一请求链中被调用的次数超过 16 次,Lambda 会自动停止该请求链中的下一次函数调用并向您发送通知。如果您的函数配置了多个触发器,来自其他触发器的调用则不会受到影响。

注意

即使在源队列的重新驱动策略的 maxReceiveCount 设置高于 16 时,Lambda 递归保护也不会阻止 Amazon SQS 在检测到递归循环并终止后重试消息。当 Lambda 检测到递归循环并丢弃后续调用时,它会向事件源映射返回 RecursiveInvocationException。这会增加消息的 receiveCount 值。Lambda 会继续重试该消息,并继续阻止函数调用,直到 Amazon SQS 确定已超出 maxReceiveCount 并将消息发送到配置的死信队列。

如果您为函数配置了失败时的目标死信队列,则 Lambda 还会将已停止调用的事件发送到您的目标或死信队列。为函数配置目标或死信队列时,确保不要将函数也在使用的 Amazon SNS 主题或 Amazon SQS 队列,用作事件触发器或事件源映射。如果您将事件发送到调用函数的同一资源,则可以创建另一个递归循环。

受支持的 AWS 服务 和开发工具包

Lambda 只能检测包含某些受支持的 AWS 服务 的递归循环。为了检测到递归循环,您的函数还必须使用一种受支持的 AWS 开发工具包。

支持 AWS 服务

Lambda 目前可检测您的函数、Amazon SQS、Amazon S3 和 Amazon SNS 之间的递归循环。Lambda 还会检测仅由 Lambda 函数组成的循环,这些函数可以同步或异步地相互调用。下图显示了 Lambda 可以检测的一些循环示例:

Lambda 函数、Amazon SNS、Amazon S3 和 Amazon SQS 队列之间的递归循环图。

如果 Amazon DynamoDB 等其他 AWS 服务构成循环的一部分,Lambda 目前无法对其进行检测和阻止。

由于 Lambda 目前仅检测涉及 Amazon SQS、Amazon S3 和 Amazon SNS 的递归循环,因此涉及其他 AWS 服务的循环仍有可能导致您的 Lambda 函数遭意外使用。

为防止 AWS 账户 产生意外费用,我们建议您配置 Amazon CloudWatch 警报,提醒自己注意异常使用模式。例如,您可以将 CloudWatch 配置为在 Lambda 函数并发或调用出现峰值时向自己发送通知。您还可以配置账单警报,以便在账户中的支出超过指定的阈值时通知您。您也可以使用 AWS Cost Anomaly Detection 来提醒自己注意异常的计费模式。

受支持的 AWS 开发工具包

要让 Lambda 检测递归循环,您的函数必须使用以下开发工具包版本中的一种版本或更高版本:

运行时系统 所需 AWS 开发工具包的最低版本

Node.js

2.1147.0(开发工具包版本 2)

3.105.0(开发工具包版本 3)

Python

1.24.46(boto3)

1.27.46(botocore)

Java 8 和 Java 11

2.17.135

Java 17

2.20.81

Java 21

2.21.24

.NET

3.7.293.0

Ruby

3.134.0

PHP

3.232.0

Go

SDK V2(使用最新版本)

某些 Lambda 运行时系统(例如 Python 和 Node.js)包含 AWS 开发工具包的一个版本。如果函数运行时系统中包含的开发工具包版本低于所需的最低版本,您可以将受支持的开发工具包版本添加到函数的部署包中。您还可以使用 Lambda 层将受支持的开发工具包版本添加到自己的函数中。有关每个 Lambda 运行时系统包含的开发工具包的列表,请参阅 Lambda 运行时

递归循环通知

当 Lambda 停止递归循环时,您会通过 AWS Health Dashboard 或电子邮件收到通知。您还可以使用 CloudWatch 指标来监控 Lambda 已停止的递归的调用次数。

AWS Health Dashboard 通知

当 Lambda 停止递归调用时,AWS Health Dashboard 会在账户运行状况页面的未决问题和近期问题下显示一条通知。请注意,在 Lambda 停止递归调用后,最多可能需要三个小时才能显示此通知。有关在 AWS Health Dashboard 中查看账户事件的更多信息,请参阅《AWS 运行状况用户指南》中的 Getting started with your AWS Health Dashboard – Your account health

电子邮件警报

在首次停止函数的递归调用时,Lambda 会向您发送电子邮件警报。Lambda 每 24 小时最多为您 AWS 账户 中的每个函数发送一封电子邮件。在 Lambda 发送电子邮件通知后,即使 Lambda 停止对该函数的进一步递归调用,您也不会在接下来的 24 小时内再收到该函数的更多电子邮件。请注意,在 Lambda 停止递归调用后,最多可能需要三个小时您才会收到此通知。

Lambda 会向您 AWS 账户 的主要账户联系人和备用运营联系人发送递归循环电子邮件警报。有关查看或更新账户中电子邮件地址的信息,请参阅《AWS 一般参考》中的 Updating contact information

Amazon CloudWatch 指标

CloudWatch 指标 RecursiveInvocationsDropped 会记录 Lambda 因您的函数在单个请求链中被调用次数超过约 16 次而停止的函数调用次数。Lambda 会在停止递归调用后立即发出此指标。要查看此指标,请按照在 CloudWatch 控制台上查看指标的说明进行操作,然后选择指标 RecursiveInvocationsDropped

响应递归循环检测通知

当同一触发事件调用您的函数超过约 16 次时,Lambda 会停止该事件的下一次函数调用,以便中断递归循环。为防止 Lambda 中断的递归循环再次出现,请执行以下操作:

  • 将函数的可用并发减少到零,即可限制未来发生的所有调用。

  • 移除或禁用会调用函数的触发器或事件源映射。

  • 识别并修复会将事件写回会调用函数的 AWS 资源的代码缺陷。在使用变量定义函数的事件源和目标时,就会出现常见的缺陷来源。请检查并确认您为两个变量使用的是不同值。

此外,如果您的 Lambda 函数的事件源是 Amazon SQS 队列,则可以考虑在源队列上配置死信队列

注意

确保在源队列上配置死信队列,而不是在 Lambda 函数上配置。您在函数上配置的死信队列用于函数的异步调用队列,而不是用于事件源队列。

如果事件源是 Amazon SNS 主题,请考虑为您的函数添加失败时的目标

将函数的可用并发减少到零(控制台)
  1. 打开 Lamba 控制台的函数页面

  2. 选择函数的名称。

  3. 选择限制

  4. 限制函数对话框中,选择确认

删除函数的触发器或事件源映射(控制台)
  1. 打开 Lamba 控制台的函数页面

  2. 选择函数的名称。

  3. 选择配置选项卡,然后选择触发器

  4. 触发器下,选择要删除的触发器或事件源映射,然后选择删除

  5. 删除触发器对话框中,选择删除

禁用函数的事件源映射(AWS CLI)
  1. 要找到要禁用的事件源映射的 UUID,请运行 AWS Command Line Interface(AWS CLI)list-event-source-mappings 命令。

    aws lambda list-event-source-mappings
  2. 要禁用事件源映射,请运行以下 AWS CLI update-event-source-mapping 命令。

    aws lambda update-event-source-mapping --function-name MyFunction \ --uuid a1b2c3d4-5678-90ab-cdef-EXAMPLE11111 --no-enabled

允许 Lambda 函数在递归循环中运行

如果您的设计有意使用递归循环,则可以配置 Lambda 函数以允许递归调用该函数。我们建议避免在设计中使用递归循环。实施错误可能导致递归调用使用您 AWS 账户 的所有可用并发量,并向您的账户收取意外费用。

重要

如果您使用递归循环,请谨慎对待。实施最佳实践防护轨道,以最大限度地降低实施错误的风险。要了解使用递归模式的最佳实践的更多信息,请参阅 Serverless Land 中的 Recursive patterns that cause run-away Lambda functions

您可以使用 Lambda 控制台、AWS Command Line Interface(AWS CLI)和 PutFunctionRecursionConfig API 将函数配置为允许递归循环。您也可以在 AWS SAM 和 AWS CloudFormation 中配置函数的递归循环检测设置。

默认情况下,Lambda 会检测并终止递归循环。除非您的设计有意使用递归循环,否则我们建议您不要更改函数的默认配置。

请注意,当您将函数配置为允许递归循环时,不会发出 CloudWatch 指标 RecursiveInvocationsDropped

Console
允许函数在递归循环中运行(控制台)
  1. 打开 Lamba 控制台的函数页面

  2. 选择函数的名称,打开函数详细信息页面。

  3. 选择配置选项卡,然后选择并发和递归检测

  4. 递归循环检测旁边,选择编辑

  5. 选择允许递归循环

  6. 选择保存

AWS CLI

您可以使用 PutFunctionRecursionConfig API 来允许在递归循环中调用您的函数。为递归循环参数指定 Allow。例如,您可以使用 put-function-recursion-config AWS CLI 命令调用此 API:

aws lambda put-function-recursion-config --function-name yourFunctionName --recursive-loop Allow

您可以将函数的配置更改回默认设置,以便 Lambda 在检测到递归循环时将其终止。使用 Lambda 控制台或 AWS CLI 编辑函数的配置。

Console
配置函数以终止递归循环(控制台)
  1. 打开 Lamba 控制台的函数页面

  2. 选择函数的名称,打开函数详细信息页面。

  3. 选择配置选项卡,然后选择并发和递归检测

  4. 递归循环检测旁边,选择编辑

  5. 选择终止递归循环

  6. 选择保存

AWS CLI

您可以使用 PutFunctionRecursionConfig API 来配置您的函数,这样 Lambda 在检测到递归循环时将其终止。为递归循环参数指定 Terminate。例如,您可以使用 put-function-recursion-config AWS CLI 命令调用此 API:

aws lambda put-function-recursion-config --function-name yourFunctionName --recursive-loop Terminate

支持 Lambda 递归循环检测的区域

以下 AWS 区域 中支持 Lambda 递归循环检测。

  • 美国东部(弗吉尼亚州北部)

  • 美国东部(俄亥俄州)

  • 美国西部(加利福尼亚北部)

  • 美国西部(俄勒冈州)

  • 非洲(开普敦)

  • 亚太地区(香港)

  • 亚太地区(雅加达)

  • 亚太地区(孟买)

  • 亚太地区(大阪)

  • 亚太地区(首尔)

  • 亚太地区(新加坡)

  • 亚太地区(悉尼)

  • 亚太地区(东京)

  • 加拿大(中部)

  • 欧洲地区(法兰克福)

  • 欧洲地区(爱尔兰)

  • 欧洲地区(伦敦)

  • 欧洲地区(米兰)

  • 欧洲地区(巴黎)

  • 欧洲地区(斯德哥尔摩)

  • 中东(巴林)

  • 南美洲(圣保罗)