Lambda 函数扩展 - AWS Lambda

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

Lambda 函数扩展

并发是您的 AWS Lambda 函数同时处理的正在进行的请求数。对于每个并发请求,Lambda 会预置单独的执行环境实例。当您的函数收到更多请求时,Lambda 会自动处理执行环境数量的扩展,直到您达到账户的并发限制。默认情况下,Lambda 为您的账户提供的一个 AWS 区域 中所有函数总并发上限为 1000 个并发执行。为了支持您的特定账户需求,您可以申请增加限额,并配置函数级并发控制,这样您的关键函数就不会节流。

本主题介绍了 Lambda 中的并发和函数横向缩减。在本主题结束时,您将能够了解如何计算并发、如何可视化两个主要的并发控制选项(预留类和预置类)、估计适当的并发控制设置,以及查看用于进一步优化的指标。

了解和可视化并发

Lambda 调用一个安全和隔离的执行环境中的函数。要处理请求,Lambda 必须先初始化执行环境(Init 阶段),然后再使用它来调用您的函数(Invoke 阶段):


        执行环境的典型生命周期,显示 Init 和 Invoke 阶段。
注意

实际的 Init 和 Invoke 持续时间可能因多种因素而异,例如您选择的运行时和 Lambda 函数代码。前面的图的目的并不是表示 Init 和 Invoke 阶段持续时间的确切比例。

上图使用矩形表示单个执行环境。当函数收到其第一个请求(由带标签 1 的黄色圆圈表示)时,Lambda 会创建一个新的执行环境并在初始化阶段在主处理程序之外运行代码。然后,Lambda 在 Invoke 阶段运行函数的主处理程序代码。在整个过程中,此执行环境繁忙,无法处理其他请求。

当 Lambda 处理完第一个请求后,此执行环境就可以处理针对同一函数的其他请求。对于后续请求,Lambda 无需重新初始化环境。


        连续处理两个请求的执行环境。

在上图中,Lambda 重复使用执行环境来处理第二个请求(由带标签 2 的黄色圆圈表示)。

到目前为止,我们只关注您的执行环境的单个实例(即并发为 1)。实际上,Lambda 可能需要并行预置多个执行环境实例来处理所有传入请求。当您的函数收到新请求时,可能会发生以下两种情况之一:

  • 如果预初始化的执行环境实例可用,Lambda 会使用它来处理请求。

  • 否则,Lambda 会创建一个新的执行环境实例来处理请求。

例如,让我们来看看当您的函数收到 10 个请求时会发生什么:


        处理 10 个请求的 Lambda 函数。它必须预置多个环境来处理所有请求。

在上图中,每个水平平面代表一个执行环境实例(标记为从 AF)。以下是 Lambda 处理每个请求的方式:

请求 1 到 10 的 Lambda 行为
请求 Lambda 行为 Reasoning

1

预置新环境 A

这是第一个请求;没有可用的执行环境实例。

2

预置新环境 B

现有执行环境实例 A 繁忙。

3

预置新环境 C

现有执行环境实例 AB 都繁忙。

4

预置新环境 D

现有执行环境实例 ABC 都繁忙。

5

预置新环境 E

现有执行环境实例 ABCD 都繁忙。

6

重用环境 A

执行环境实例 A 已处理完请求 1,现已可用。

7

重用环境 B

执行环境实例 B 已处理完请求 2,现已可用。

8

重用环境 C

执行环境实例 C 已处理完请求 3,现已可用。

9

预置新环境 F

现有执行环境实例 ABCDE 都繁忙。

10

重用环境 D

执行环境实例 D 已处理完请求 4,现已可用。

随着您的函数收到更多的并发请求,Lambda 会纵向扩展响应中的执行环境实例的数量。以下动画跟踪一段时间内的并发请求数:


        说明一段时间内并发请求的动画。

通过将之前的动画冻结在六个不同的时间点,我们得到下图:


        在六个不同时间点的函数并发。

在上图中,我们可以在任何时间点绘制一条垂直线,并计算与该直线相交的环境数量。这为我们提供了该时间点的并发请求数。例如,时间 t1 处有三个处于活动状态的环境在处理三个并发请求。此模拟中的最大并发请求数发生在时间 t4,此时有六个处于活动状态的环境处理六个并发请求。

总而言之,函数并发是它同时处理并发请求的数目。为了应对函数并发的增加,Lambda 预置了更多的执行环境实例以满足请求需求。

如何计算并发

通常,系统的并发是指同时处理多个任务的能力。在 Lambda 中,并发是您的函数同时处理的正在进行的请求数。一种衡量 Lambda 函数并发的快速而实用的方法是使用以下公式:

Concurrency = (average requests per second) * (average request duration in seconds)

并发不同于每秒的请求数。例如,假设您的函数平均每秒接收 100 个请求。如果平均请求持续时间为一秒,那么并发确实也是 100:

Concurrency = (100 requests/second) * (1 second/request) = 100

但是,如果平均请求持续时间为 500 毫秒,则并发为 50:

Concurrency = (100 requests/second) * (0.5 second/request) = 50

实际上,并发为 50 意味着什么? 如果平均请求持续时间为 500 毫秒,则可以将函数的实例视为每秒能够处理两个请求。然后,您的函数需要 50 个实例才能处理每秒 100 个请求的负载。并发为 50 意味着 Lambda 必须预置 50 个执行环境实例才能在没有任何节流的情况下高效处理此工作负载。以下是用方程式表示这种情况的方法:

Concurrency = (100 requests/second) / (2 requests/second) = 50

如果您的函数收到的请求数是原来的两倍(每秒 200 个请求),但只需要一半的时间来处理每个请求(250 毫秒),则并发仍为 50:

Concurrency = (200 requests/second) * (0.25 second/request) = 50

假设您有一个平均运行时间为 200 毫秒的函数。在峰值负载期间,可每秒观察 5000 个请求。在峰值负载期间,您的函数的并发是多少?

函数的平均持续时间为 200 毫秒或 0.2 秒。使用并发公式,您可以插入数字以获取 1,000 的并发:

Concurrency = (5,000 requests/second) * (0.2 seconds/request) = 1,000

或者,函数平均持续时间为 200 毫秒意味着您的函数每秒可处理 5 个请求。要处理每秒 5000 个请求的工作负载,您需要 1000 个执行环境实例。因此,并发为 1000:

Concurrency = (5,000 requests/second) / (5 requests/second) = 1,000

并发与每秒的请求数

如上一节中所述,并发不同于每秒的请求数。在处理平均请求持续时间小于 100 毫秒的函数时,这是一项特别重要的区别。

通常,执行环境的每个实例每秒最多可以处理 10 个请求。此限制适用于同步按需函数,以及使用预置并发的函数。如果您不熟悉此限制,则可能不清楚为何此类函数在某些场景中会遇到节流。

例如,考虑一个平均请求持续时间为 50 毫秒的函数。在每秒 200 个请求时,以下是此函数的并发:

Concurrency = (200 requests/second) * (0.05 second/request) = 10

根据此结果,您可能预计只需 10 个执行环境实例即可处理此负载。但是,每个执行环境每秒只能处理 10 次执行。这意味着在 10 个执行环境中,您的函数每秒只能处理 200 个请求总数中的 100 个请求。此函数会受到节流。

教训是,在为函数配置并发设置时,必须同时考虑并发和每秒请求数。在这种情况下,您的函数需要 20 个执行环境,即使它的并发只有 10 个也是如此。

假设您有一个平均运行时间为 20 毫秒的函数。在峰值负载期间,可每秒观察 3000 个请求。在峰值负载期间,您的函数的并发是多少?

函数的平均持续时间为 20 毫秒或 0.02 秒。使用并发公式,您可以插入数字以获取 60 的并发:

Concurrency = (3,000 requests/second) * (0.02 seconds/request) = 60

但是,每个执行环境每秒只能处理 10 个请求。在 60 个执行环境中,您的函数每秒最多可以处理 600 个请求。为了完全适应 3000 个请求,您的函数至少需要 300 个执行环境实例。

预留并发和预置并发。

默认情况下,您的账户有某个区域内的所有函数的并发上限,该上限为 1000 个并行执行。您的函数按需共享这个拥有 1000 个并发的并发池。如果您用尽了可用的并发,您的函数将节流(即开始丢弃请求)。

您的某些函数可能比其他函数更重要。因此,您可能需要配置并发设置,以确保关键函数获得所需的并发。有两种并发控制:预留并发和预置并发。

  • 使用预留并发可为函数预留账户并发的某部分。如果您不想让其他函数占用所有可用的非预留并发,这非常有用。

  • 使用预置并发为一个函数预先初始化多个环境实例。这对于减少冷启动延迟很有用。

预留并发

若想保证您的函数在任何时候都有一定数量的并发可用,请使用预留并发。

预留并发是您要分配给函数的最大并发实例数。当将预留并发提供给函数时,任何其他函数都不可以使用该并发。换言之,设置预留并发会影响可用于其他函数的并发池。没有预留并发的函数共享剩余的非预留并发池。

配置预留并发将计入您的账户总并发上限。为函数配置预留并发不收取任何费用。

为了更好地理解预留并发,请细看下图:


          您在关键函数上配置预留并发时的函数扩展行为。

在此图中,此区域中的所有函数的账户并发限制为默认限制 1000。假设您有两个关键函数 function-bluefunction-orange,它们通常会预估获得很高的调用量。您决定将 400 个单位的预留并发分配给 function-blue,将 400 个单位的预留并发分配给 function-orange。在此示例中,您账户中的所有其他函数必须共享剩余的 200 个单位的非预留并发。

该图有五个兴趣点:

  • t1function-orangefunction-blue 都开始接收请求。每个函数开始用完其预留并发单位的分配部分。

  • t2function-orangefunction-blue 可稳步接收更多请求。同时,您部署了某些其他 Lambda 函数,这些函数开始接收请求。您不将预留并发分配给这些其他函数。它们开始使用剩余的 200 个单位的非预留并发。

  • t3function-orange 达到最大并发 400。尽管您的账户中的其他地方有未使用的并发,但 function-orange 无法访问它。红线表示 function-orange 处于节流状态,Lambda 可能会丢弃请求。

  • t4function-orange 开始接收更少的请求并且不再节流。但是,您的其他函数会遇到流量峰值并开始节流。尽管您的账户中的其他地方有未使用的并发,但其他函数无法访问它。红线表示您的其他函数处于节流状态。

  • t5,其他函数开始接收更少的请求并且不再节流。

在此示例中,请注意,预留并发具有以下效果:

  • 您的函数可以独立于账户中的其他函数进行扩缩。在没有预留并发的同一区域中,您所有账户的函数共享非预留并发池。如果没有预留并发,其他函数可能会耗尽所有您的可用的并发。从而导致关键函数无法根据需要进行纵向扩展。

  • 您的函数不能无节制地扩缩。预留并发对函数的最大并发设置了上限。这意味着您的函数不能使用为其他函数预留的并发,也不能使用非预留池中的并发。您可以预留并发以防止您的函数使用您账户中的所有可用并发,或者防止下游资源过载。

  • 您可能无法使用账户的所有可用并发。预留并发计入您的账户并发上限,但这也意味着其他函数无法使用这一大部分预留并发。如果您的函数没有用完您为它预留的所有并发,那么您实际上是在浪费这个并发。除非您账户中的其他函数可以从浪费的并发中受益,否则这不是问题。

要了解如何管理函数的预留并发设置,请参阅 配置预留并发

预配置并发

您可以使用预留并发来定义为 Lambda 函数预留的最大执行环境数。但是,这些环境都不会进行预先初始化。因此,您的函数调用可能需要更长的时间,因为 Lambda 必须先初始化新环境,然后才能使用它来调用您的函数。当 Lambda 必须初始化新环境才能执行调用时,这称为冷启动。为了减少冷启动,您可以使用预置并发。

预置并发是您要分配给函数的预初始化执行环境的数量。如果您在函数上设置预置并发,Lambda 会初始化该执行环境数量,以便它们准备好立即响应函数请求。

注意

使用预置并发会让您的账户产生费用。如果您使用的是 Java 11 或 Java 17 运行时,也可以使用 SnapStart Lambda 来缓解冷启动问题,无需支付额外费用。 SnapStart 使用执行环境的缓存快照来显著提高启动性能。您不能在同一个函数版本上同时使用这两个函数 SnapStart 和预配置的并发性。有关 SnapStart 功能、限制和支持的区域的更多信息,请参阅使用 Lambda 提高创业绩效 SnapStart

当使用预置并发时,Lambda 仍会在后台回收执行环境。但是,在任何给定时间,Lambda 始终确保预初始化环境的数量等于您的函数预置并发设置的值。这种行为与预留并发不同,在预留并发中,Lambda 可能会在不活动时段后完全终止环境。当您使用预留并发而不是预置并发来配置函数时,下图通过比较单个执行环境的生命周期来说明这一点。


          保留并发模型与预置并发模型下的函数环境行为有何不同。

该图有四个兴趣点:

时间 预留并发 预配置并发

t1

什么都未发生。

Lambda 预初始化一个执行环境实例。

t2

请求 1 传入。Lambda 必须初始化一个新的执行环境实例。

请求 1 传入。Lambda 使用预初始化的环境实例。

t3

在经过一段时间的不活动状态后,Lambda 会终止处于活动状态的环境实例。

什么都未发生。

t4

请求 2 传入。Lambda 必须初始化一个新的执行环境实例。

请求 2 传入。Lambda 使用预初始化的环境实例。

为了更好地了解预置并发,请细看下图:


          在关键函数上配置预置并发时的函数扩展行为。

在此图中,您的账户并发限制为 1,000。您决定将 400 个单位的预置并发量分配给 function-orange。您账户中的所有函数,包括 function-orange,都可以使用剩余的 600 个单位的非预留并发。

该图有五个兴趣点:

  • t1function-orange 开始接收请求。由于 Lambda 已经预先初始化 400 个执行环境实例,function-orange 可以立即调用。

  • t2function-orange 达到 400 个并发请求。因此,function-orange 已用尽预置并发。但是,由于仍有非预留并发可用,Lambda 可以使用它来处理对 function-orange 的额外请求(没有节流)。Lambda 必须创建新实例来处理这些请求,并且您的函数可能会遇到冷启动延迟。

  • t3function-orange 在短暂的流量峰值后返回 400 个并发请求。Lambda 能够再次在没有冷启动延迟的情况下处理所有请求。

  • t4,您账户中的函数会遇到流量突增的情况。这种突增可能产生于 function-orange 或您账户中的任何其他函数。Lambda 使用非预留并发来处理这些请求。

  • t5,您的账户中的函数达到 1,000 的最大并发上限,并且会节流。

前面的示例仅考虑了预置并发。实际上,您可以设置函数的预置并发和预留并发。如果您有一个函数可以处理工作日的恒定调用负载,但是在周末经常出现流量峰值,那么您可以这样做。在这种情况下,您可以使用预置并发来设置基准环境数量以在工作日处理请求,并使用预留并发来处理周末的流量峰值。请细看以下图:


          同时使用预留和预置并发时的函数扩缩行为。

在此图中,假设您为 function-orange 配置了 200 个单位的预置并发和 400 个单位的预留并发。由于您配置了预留并发,因此 function-orange 无法使用 600 个单位的非预留并发中的任何一个。

该图有五个兴趣点:

  • t1function-orange 开始接收请求。由于 Lambda 已经预先初始化 200 个执行环境实例,function-orange 可以立即调用。

  • t2function-orange 已用尽其所有的预置并发。function-orange 可以继续使用预留并发来处理请求,但这些请求可能会遇到冷启动延迟。

  • t3function-orange 达到 400 个并发请求。因此,function-orange 已用尽其所有预留并发。由于 function-orange 无法使用非预留并发,因此请求开始节流。

  • t4function-orange 开始接收更少的请求并且不再节流。

  • t5function-orange 降至 200 个并发请求,因此所有请求都能够再次使用预置并发(即没有冷启动延迟)。

预留并发和预置并发均计入您的账户并发限制和区域限额。换言之,分配预留和预置并发会影响可用于其他函数的并发池。配置预置并发会让您的 AWS 账户 产生费用。

注意

如果函数版本与别名功能上的预配置并发数加起来达到函数的预留并发,则所有调用都在预配置并发上运行。此配置还具有限制函数 ($LATEST) 未发布版本的效果,从而阻止其执行。为函数分配的预配置并发数不能超过预留并发数。

要管理函数的预置并发设置,请参阅 配置预配置并发。要根据计划或应用程序利用率自动执行预置并发扩展,请参阅 使用 Application Auto Scaling 管理预置并发

Lambda 如何分配预置并发

配置后,预置并发并不会立即生效。Lambda 会在一两分钟的准备时间后开始分配预配置并发。特别是,Lambda 可以同时预置 500 到 3000 个执行环境,具体取决于区域。此次最初突增之后,无论在哪个区域,Lambda 每分钟都会额外分配 500 个环境,直到请求完成。

例如,假设您的账户并发限制为 10000。此外,假设在美国东部(弗吉尼亚州北部)上午 10:00,您为一个函数配置了 5000 个单位的预置并发。以下是 Lambda 可能分配预置并发单位的方式:


          显示 Lambda 如何分配预置并发实例的折线图。

在上图中:

  • 最初,Lambda 最多可以预置 3000 个执行环境,因为美国东部(弗吉尼亚州北部)的初始突增并发限制为 3000。

  • 在 10:00:您为此函数请求 5000 个单位的预置并发。Lambda 不会立即开始预置执行环境。

  • 在 10:01:Lambda 首先预置 3000 个环境。

  • 从 10:02 到 10:05:Lambda 每分钟额外预置 500 个环境。到 10:05,Lambda 可完成向您的函数分配 5000 个环境。

当您提交分配预置并发的请求时,在 Lambda 完全完成分配之前,您无法访问任何这些环境。例如,在前面的场景中,在 10:05 之前,您的所有请求都无法使用预置的并发,因为此时 Lambda 会完全完成对您的 5000 个执行环境请求的分配。

对比预留并发和预置并发。

下表总结并对比了预留并发和预置并发。

主题 预留并发 预配置并发

定义

您的函数的最大执行环境实例数。

设置您的函数的预置执行环境实例数。

预置行为

Lambda 按需预置新实例。

Lambda 预置实例(即在您的函数开始接收请求之前)。

冷启动行为

由于 Lambda 必须按需创建新实例,因此可能出现冷启动延迟。

由于 Lambda 不必按需创建实例,因此不可能发生冷启动延迟。

节流行为

当达到预留并发限制时,函数会被节流。

如果未设置预留并发:当达到预置并发限制时,函数将使用非预留并发。

如果设置了预留并发:当达到预留并发限制时,函数会被节流。

如果未设置,则为默认行为

函数使用您的账户中可用的非预留并发。

Lambda 不预置任何实例。相反,如果没有设置预留并发:函数使用您的账户中可用的非预留并发。

如果设置了预留并发:函数使用预留并发。

定价

无额外费用。

会产生额外费用。

并发限额

对于可跨区域中所有函数使用的并发的总量,Lambda 可设置限额。这些限额分为两个级别:

  • 在账户级别,默认情况下,您的函数最多可以有 1000 个单位的并发。要提高此限制,请参阅 Service Quotas User Guide(《服务限额用户指南》)中的 Requesting a quota increase(请求增加限额)。

  • 在函数级别,默认情况下,您可以为所有函数保留最多 900 个单位的并发。无论您的账户总并发限制如何设置,Lambda 始终为未明确保留并发的函数预留 100 个并发单位。例如,如果您将账户并发限制提高到 2000,则可以在函数级别预留最多 1900 个单位的并发。

要检查您的当前账户级别并发限额,请使用 AWS Command Line Interface(AWS CLI)运行以下命令:

aws lambda get-account-settings

您应该会看到类似如下输出:

{ "AccountLimit": { "TotalCodeSize": 80530636800, "CodeSizeUnzipped": 262144000, "CodeSizeZipped": 52428800, "ConcurrentExecutions": 1000, "UnreservedConcurrentExecutions": 900 }, "AccountUsage": { "TotalCodeSize": 410759889, "FunctionCount": 8 } }

ConcurrentExecutions 是您的账户级别的总并发限额。UnreservedConcurrentExecutions 是您仍然可以分配给函数的预留并发。

当您的函数收到更多请求时,Lambda 会自动纵向扩展执行环境的数量来处理这些请求,直到账户达到其并发限额。但是,为了防止因突然的流量爆发而出现过度扩展,Lambda 限制了函数的扩展速度。此并发扩展速率是指您的账户中的函数可以根据请求的增加进行扩展的最大速率。(也就是 Lambda 创建新执行环境的速度。) 并发扩展速率不同于账户级别的并发限制,后者是您的函数可用的并发总量。

在每个 AWS 区域 中,对于每个函数,您的并发扩展速率为每 10 秒 1,000 个执行环境实例。换句话说,每 10 秒钟,Lambda 可以为您的每个函数最多分配 1,000 个额外的执行环境实例。

通常,您无需关注此限制。Lambda 的扩展速率足以满足大多数用例的需求。

重要的是,并发缩放速率是函数级别的限制。这意味着您账户中的每个函数可以独立于其他函数进行扩展。

有关扩展行为的更多信息,请参阅 Lambda 扩展行为