了解 Lambda 执行环境生命周期 - Amazon Lambda
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

了解 Lambda 执行环境生命周期

Lambda 在执行环境中调用您的函数,该环境提供一个安全和隔离的运行时环境。执行环境管理运行函数所需的资源。执行环境为函数的运行时以及与函数关联的任何外部扩展提供生命周期支持。

函数的运行时使用运行时 API 与 Lambda 进行通信。扩展使用扩展 API 与 Lambda 进行通信。扩展还可借助遥测 API,从该函数接收日志消息与其他遥测数据。

执行环境的体系结构图。

创建 Lambda 函数时,您需要指定配置信息,例如可用内存量和函数允许的最长执行时间。Lambda 使用此信息设置执行环境。

函数的运行时和每个外部扩展都是在执行环境中运行的进程。权限、资源、凭证和环境变量在函数和扩展之间共享。

Lambda 执行环境生命周期

Lambda 生命周期阶段:初始化、调用、关闭

每个阶段都以 Lambda 发送到运行时和所有注册的扩展的事件开始。运行时和每个扩展通过发送 Next API 请求来指示完成。当运行时和每个扩展完成且没有挂起的事件时,Lambda 会冻结执行环境。

Init 阶段

Init 阶段,Lambda 执行三项任务:

  • 开启所有扩展 (Extension init)

  • 引导运行时 (Runtime init)

  • 运行函数的静态代码 (Function init)

  • 运行任何检查点前的运行时钩子(仅限 Lambda SnapStart)

当运行时和所有扩展通过发送 Init API 请求表明它们已准备就绪时, Next 阶段结束。Init 阶段限制为 10 秒。如果所有三个任务都未在 10 秒内完成,Lambda 在第一个函数调用时使用配置的函数超时值重试 Init 阶段。

激活 Lambda SnapStart 后,在您发布一个函数版本时会发生 Init 阶段。Lambda 保存初始化的执行环境的内存和磁盘状态的快照,永久保存加密快照并对其进行缓存以实现低延迟访问。如果您具有检查点前的运行时钩子,则该代码将在 Init 阶段结束时运行。

注意

10 秒超时不适用于使用预调配并发或 SnapStart 的函数。对于预调配并发和 SnapStart 函数,初始化代码最长可能会运行 15 分钟。时间限制为 130 秒或配置的函数超时(最大 900 秒),以较高者为准。

使用预置并发时,Lambda 会在您为函数配置 PC 设置时初始化执行环境。Lambda 还确保初始化的执行环境在调用之前始终可用。您会发现函数的调用和初始化阶段之间存在时间差。根据函数的运行时系统和内存配置,在初始化的执行环境中首次调用时可能会发生一些延迟变化。

对于使用按需并发的函数,Lambda 可能会在调用请求之前偶尔初始化执行环境。发生这种情况时,您可能会观察到函数的初始化和调用阶段之间存在时间差。我们建议您不要依赖此行为。

在 Init 阶段失败

如果函数在 Init 阶段崩溃或超时,Lambda 会在日志中发出错误信息。INIT_REPORT

例 — INIT_REPORT 超时日志
INIT_REPORT Init Duration: 1236.04 ms Phase: init Status: timeout
例 — INIT_REPORT 扩展失败日志
INIT_REPORT Init Duration: 1236.04 ms Phase: init Status: error Error Type: Extension.Crash

如果 Init 阶段成功,除非启用 SnapStart预置并发,否则 Lambda 不会发出 INIT_REPORT 日志。SnapStart 和预置并发函数始终会发出 INIT_REPORT。有关更多信息,请参阅 监控 Lambda SnapStart

还原阶段(仅限 Lambda SnapStart)

当您首次调用 SnapStart 函数时,随着该函数的扩展,Lambda 会从永久保存的快照中恢复新的执行环境,而不是从头开始初始化函数。如果您有 afterRestore() 运行时挂钩,则代码将在 Restore 阶段结束时运行。afterRestore() 运行时挂钩执行期间将产生费用。必须加载运行时(JVM),并且 afterRestore() 运行时挂钩必须在超时限制(10 秒)内完成。否则,您将收到 SnapStartTimeoutException。Restore 阶段完成后,Lambda 将调用函数处理程序(调用阶段)。

在 Restore 阶段失败

如果 Restore 阶段失败,Lambda 会在 RESTORE_REPORT 日志中发出错误信息。

例 — RESTORE_REPORT 超时日志
RESTORE_REPORT Restore Duration: 1236.04 ms Status: timeout
例 — RESTORE_REPORT 运行时系统钩子失败日志
RESTORE_REPORT Restore Duration: 1236.04 ms Status: error Error Type: Runtime.ExitError

有关 RESTORE_REPORT 日志的更多信息,请参阅 监控 Lambda SnapStart

调用阶段

当调用 Lambda 函数以响应 Next API 请求时,Lambda 向运行时和每个扩展发送一个 Invoke 事件。

函数的超时设置限制了整个 Invoke 阶段的持续时间。例如,如果将函数超时设置为 360 秒,则该函数和所有扩展都需要在 360 秒内完成。请注意,没有独立的调用后阶段。持续时间是所有调用时间(运行时 + 扩展)的总和,直到函数和所有扩展完成执行之后才计算。

调用阶段在运行时之后结束,所有扩展都通过发送 Next API 表示它们已完成。

在调用阶段失败

如果 Lambda 函数在 Invoke 阶段崩溃或超时,Lambda 会重置执行环境。下图演示了调用失败时的 Lambda 执行环境行为:

执行环境示例:初始化、调用、调用时出错、调用、关闭

在上图中:

  • 第一个阶段是 INIT 阶段,运行没有错误。

  • 第二个阶段是 INVOKE 阶段,运行没有错误。

  • 假设您的函数在某个时点遇到调用失败的问题(例如函数超时或运行时错误)。标签为 INVOKE WITH ERROR 的第三个阶段演示了这种情况。出现这种情况时,Lambda 服务会执行重置。重置的行为类似于 Shutdown 事件。首先,Lambda 会关闭运行时,然后向每个注册的外部扩展发送一个 Shutdown 事件。该事件包括关闭的原因。如果此环境用于新调用,则 Lambda 会将扩展和运行时与下一次调用一起重新初始化。

    请注意,Lambda 重置不会在下一个初始化阶段之前清除 /tmp 目录内容。这种行为与常规关闭阶段一致。

    注意

    Amazon 目前正在实施对 Lambda 服务的更改。由于这些更改,您可能会看到 Amazon Web Services 账户 中不同 Lambda 函数发出的系统日志消息和跟踪分段的结构和内容之间存在细微差异。

    如果您的函数的系统日志配置设置为纯文本,则当您的函数遇到调用失败时,此更改会影响在 CloudWatch Logs 中捕获的日志消息。以下示例显示了新旧格式的日志输出。

    这些更改将在未来几周内实施,除中国和 GovCloud 区域外,所有 Amazon Web Services 区域 的函数都将过渡到使用新格式的日志消息和跟踪分段。

    例 CloudWatch Logs 日志输出(运行时或扩展崩溃)(旧样式)
    START RequestId: c3252230-c73d-49f6-8844-968c01d1e2e1 Version: $LATEST RequestId: c3252230-c73d-49f6-8844-968c01d1e2e1 Error: Runtime exited without providing a reason Runtime.ExitError END RequestId: c3252230-c73d-49f6-8844-968c01d1e2e1 REPORT RequestId: c3252230-c73d-49f6-8844-968c01d1e2e1 Duration: 933.59 ms Billed Duration: 934 ms Memory Size: 128 MB Max Memory Used: 9 MB
    例 CloudWatch Logs 日志输出(函数超时)(旧样式)
    START RequestId: b70435cc-261c-4438-b9b6-efe4c8f04b21 Version: $LATEST 2024-03-04T17:22:38.033Z b70435cc-261c-4438-b9b6-efe4c8f04b21 Task timed out after 3.00 seconds END RequestId: b70435cc-261c-4438-b9b6-efe4c8f04b21 REPORT RequestId: b70435cc-261c-4438-b9b6-efe4c8f04b21 Duration: 3004.92 ms Billed Duration: 3000 ms Memory Size: 128 MB Max Memory Used: 33 MB Init Duration: 111.23 ms

    CloudWatch 日志的新格式在 REPORT 行中包含一个附加 status 字段。在运行时或扩展崩溃的情况下,REPORT 行还包含一个字段 ErrorType

    例 CloudWatch Logs 日志输出(运行时或扩展崩溃)(新样式)
    START RequestId: 5b866fb1-7154-4af6-8078-6ef6ca4c2ddd Version: $LATEST END RequestId: 5b866fb1-7154-4af6-8078-6ef6ca4c2ddd REPORT RequestId: 5b866fb1-7154-4af6-8078-6ef6ca4c2ddd Duration: 133.61 ms Billed Duration: 133 ms Memory Size: 128 MB Max Memory Used: 31 MB Init Duration: 80.00 ms Status: error Error Type: Runtime.ExitError
    例 CloudWatch Logs 日志输出(函数超时)(新样式)
    START RequestId: 527cb862-4f5e-49a9-9ae4-a7edc90f0fda Version: $LATEST END RequestId: 527cb862-4f5e-49a9-9ae4-a7edc90f0fda REPORT RequestId: 527cb862-4f5e-49a9-9ae4-a7edc90f0fda Duration: 3016.78 ms Billed Duration: 3016 ms Memory Size: 128 MB Max Memory Used: 31 MB Init Duration: 84.00 ms Status: timeout
  • 第四个阶段是调用失败后立即进入的 INVOKE 阶段。在这里,Lambda 通过重新运行 INIT 阶段重新初始化环境。此情况称为隐藏初始化。出现隐藏初始化时,Lambda 不会在 CloudWatch Logs 中显式报告额外的 INIT 阶段。相反,您可能会注意到 REPORT 行中的持续时间包括一个额外的 INIT 持续时间 + INVOKE 持续时间。例如,假设您在 CloudWatch 中看到以下日志:

    2022-12-20T01:00:00.000-08:00 START RequestId: XXX Version: $LATEST 2022-12-20T01:00:02.500-08:00 END RequestId: XXX 2022-12-20T01:00:02.500-08:00 REPORT RequestId: XXX Duration: 3022.91 ms Billed Duration: 3000 ms Memory Size: 512 MB Max Memory Used: 157 MB

    在此例中,REPORT 和 START 时间戳的间隔为 2.5 秒。这与报告的持续时间(3022.91 毫秒)不一致,因为它没有考虑 Lambda 执行的额外 INIT(隐藏初始化)。在此例中,您可以推断出实际的 INVOKE 阶段用时 2.5 秒。

    要更深入地了解这种行为,您可以使用 使用遥测 API 访问扩展的实时遥测数据。每当在调用阶段之间出现隐藏初始化时,Telemetry API 都会发出 INIT_STARTINIT_RUNTIME_DONEINIT_REPORT 事件以及 phase=invoke

  • 第五个阶段是 SHUTDOWN 阶段,该阶段运行没有错误。

关闭阶段

若 Lambda 即将关闭运行时,它会向每个已注册的外部扩展发送一个 Shutdown 事件。扩展可以使用此时间执行最终清理任务。Shutdown 事件是对 Next API 请求的响应。

持续时间:整个 Shutdown 阶段的上限为 2 秒。如果运行时或任何扩展没有响应,则 Lambda 会通过一个信号 (SIGKILL) 终止它。

在函数和所有扩展完成后,Lambda 维护执行环境一段时间,以预期另一个函数调用。但是,Lambda 每隔几个小时就会终止执行环境,以便进行运行时更新和维护,即使是连续调用的函数亦不例外。您不应假设执行环境将无限期持续。有关更多信息,请参阅 在函数中实施无状态性

当再次调用该函数时,Lambda 会解冻环境以便重复使用。重复使用执行环境会产生以下影响:

  • 在该函数的处理程序方法的外部声明的对象保持已初始化的状态,再次调用函数时提供额外的优化功能。例如,如果您的 Lambda 函数建立数据库连接,而不是重新建立连接,则在后续调用中使用原始连接。建议您在代码中添加逻辑,以便在创建新连接之前检查是否存在连接。

  • 每个执行环境都在 /tmp 目录中提供 512MB 到 10240MB 之间的磁盘空间(以 1MB 递增)。冻结执行环境时,目录内容会保留,同时提供可用于多次调用的暂时性缓存。您可以添加额外的代码来检查缓存中是否有您存储的数据。有关部署大小限制的更多信息,请参阅Lambda 配额

  • 如果 Lambda 重复使用执行环境,则由 Lambda 函数启动但在函数结束时未完成的后台进程或回调将继续执行。确保代码中的任何后台进程或回调在代码退出前已完成。

冷启动和延迟

当 Lambda 收到通过 Lambda API 运行函数的请求时,该服务会首先准备执行环境。在此初始化阶段,该服务会下载您的代码,启动环境,并在主处理程序之外运行任何初始化代码。最后,Lambda 会运行处理程序代码。

性能优化图 1

在此图中,下载代码和设置环境的前两个步骤通常称为“冷启动”。您无需为此时间付费,但这确实会增加总调用持续时间的延迟。

调用完成后,执行环境将被冻结。为了改善资源管理和性能,Lambda 会在一段时间内保留执行环境。在此期间,如果收到另一个针对相同函数的请求,则 Lambda 可重复使用该环境。由于执行环境已经完全设置,第二个请求通常会更快地完成。这称为“暖启动”。

冷启动通常发生在低于 1% 的调用中。冷启动的持续时间从低于 100 毫秒到超过 1 秒不等。通常,在开发和测试函数中,冷启动通常比生产工作负载更常见。这是因为开发和测试函数的调用频率通常较低。

使用预调配并发减少冷启动

如果您的工作负载需要可预测的函数启动时间,则推荐使用预置并发解决方案,以确保尽可能降低延迟。此功能可预先初始化执行环境,从而减少冷启动。

例如,预置并发数为 6 的函数预热了 6 个执行环境。

性能优化图 4

优化静态初始化

静态初始化发生在处理程序代码开始在函数中运行之前。这是您提供的初始化代码,位于主处理程序之外。此代码通常用于导入库和依赖项、设置配置和初始化与其他服务的连接。

以下 Python 示例显示了在调用期间运行 lambda_handler 函数之前,在初始化阶段导入和配置模块以及创建 Amazon S3 客户端。

import os import json import cv2 import logging import boto3 s3 = boto3.client('s3') logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): # Handler logic...

函数执行前延迟的最大因素来自初始化代码。此代码在首次创建新的执行环境时运行。如果调用使用暖执行环境,则不会再次运行初始化代码。影响初始化代码延迟的因素包括:

  • 函数包的大小,包括导入的库和依赖项以及 Lambda 层。

  • 代码量和初始化工作。

  • 库和其他服务在设置连接和其他资源方面的表现。

开发人员可以采取多个步骤来优化静态初始化延迟。如果一个函数具有许多对象和连接,您可以将单一函数重新架构为多个专用函数。它们各自较小并且都具有较少的初始化代码。

重要的是,函数只导入其需要的库和依赖项。例如,如果您仅在 Amazon SDK 中使用 Amazon DynamoDB,则可能需要单独的服务而不是整个 SDK。比较以下三个示例:

// Instead of const AWS = require('aws-sdk'), use:
const DynamoDB = require('aws-sdk/clients/dynamodb')

// Instead of const AWSXRay = require('aws-xray-sdk'), use:
const AWSXRay = require('aws-xray-sdk-core')

// Instead of const AWS = AWSXRay.captureAWS(require('aws-sdk')), use:
const dynamodb = new DynamoDB.DocumentClient()
AWSXRay.captureAWSClient(dynamodb.service)

静态初始化通常也是打开数据库连接的最佳位置,以允许函数在对同一执行环境的多次调用中重复使用连接。但是,您可能具有大量仅在函数的某些执行路径中使用的对象。在这种情况下,您可以在全局作用域内延迟加载变量,以缩短静态初始化持续时间。

避免使用全局变量来获取特定于上下文的信息。如果您的函数有一个全局变量,该变量仅在单一调用的生命周期内使用,并且在下一次调用时重置,请使用处理程序局部的变量作用域。这不仅可以防止调用间的全局变量泄漏,还可以提高静态初始化性能。