

# 了解 Lambda 执行环境生命周期
<a name="lambda-runtime-environment"></a>

Lambda 执行环境既支持标准函数（最长运行 15 分钟），也支持持久性函数（最长运行一年）。尽管两者具有相同的基本生命周期，但持久性函数还为长时间运行的工作流增添了状态管理功能。

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

**对于持久性函数，执行环境包括用于以下内容的其他组件：**
+ 步骤之间的状态持久性
+ 检查点管理
+ 等待状态协调
+ 进度跟踪

**Lambda 托管实例执行环境**  
如果您使用的是 [Lambda 托管实例](lambda-managed-instances-execution-environment.md)，则执行环境与 Lambda（默认）函数相比存在重要区别。托管实例支持并发调用，使用不同的生命周期模型，并在客户拥有的基础设施上运行。有关托管实例执行环境的详细信息，请参阅[了解 Lambda 托管实例执行环境](lambda-managed-instances-execution-environment.md)。

函数的运行时使用[运行时 API](runtimes-api.md) 与 Lambda 进行通信。扩展使用[扩展 API](runtimes-extensions-api.md) 与 Lambda 进行通信。扩展还可借助[遥测 API](telemetry-api.md)，从该函数接收日志消息与其他遥测数据。



![\[执行环境的体系结构图。\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/telemetry-api-concept-diagram.png)


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

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

**Topics**
+ [

## Lambda 执行环境生命周期
](#runtimes-lifecycle)
+ [

## 冷启动和延迟
](#cold-start-latency)
+ [

## 使用预调配并发减少冷启动
](#cold-starts-pc)
+ [

## 优化静态初始化
](#static-initialization)

## Lambda 执行环境生命周期
<a name="runtimes-lifecycle"></a>

![\[Lambda 生命周期阶段：初始化、调用、关闭\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/Overview-Successful-Invokes.png)


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

**持久性函数的生命周期阶段包括：**
+ **初始化：**标准初始化加上持久状态设置
+ **调用：**可以包括具有自动检查点机制的多个步骤执行
+ **等待：**函数可以在不消耗资源的情况下暂停执行
+ **恢复：**函数从上一个检查点重新启动
+ **关闭：**清理持久状态和资源

**Topics**
+ [

### Init 阶段
](#runtimes-lifecycle-ib)
+ [

### 在 Init 阶段失败
](#runtimes-lifecycle-init-errors)
+ [

### 还原阶段（仅限 Lambda SnapStart）
](#runtimes-lifecycle-restore)
+ [

### 调用阶段
](#runtimes-lifecycle-invoke)
+ [

### 在调用阶段失败
](#runtimes-lifecycle-invoke-with-errors)
+ [

### 关闭阶段
](#runtimes-lifecycle-shutdown)

### Init 阶段
<a name="runtimes-lifecycle-ib"></a>

在 `Init` 阶段，Lambda 执行三项任务：
+ 开启所有扩展 (`Extension init`)
+ 引导运行时 (`Runtime init`)
+ 运行函数的静态代码 (`Function init`)
+ 运行任何检查点前的[运行时钩子](snapstart-runtime-hooks.md)（仅限 Lambda SnapStart）

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

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

**注意**  
10 秒超时不适用于使用预置并发、SnapStart 或 Lambda 托管实例的函数。对于预置并发、SnapStart 和托管实例函数，初始化代码最长可能会运行 15 分钟。时间限制为 130 秒或配置的函数超时（最大 900 秒），以较高者为准。

使用[预置并发](https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html)时，Lambda 会在您为函数配置 PC 设置时初始化执行环境。Lambda 还确保初始化的执行环境在调用之前始终可用。您会发现函数的调用和初始化阶段之间存在时间差。根据函数的运行时系统和内存配置，在初始化的执行环境中首次调用时可能会发生一些延迟变化。

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

### 在 Init 阶段失败
<a name="runtimes-lifecycle-init-errors"></a>

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

**Example — INIT\$1REPORT 超时日志**  

```
INIT_REPORT Init Duration: 1236.04 ms Phase: init Status: timeout
```

**Example — INIT\$1REPORT 扩展失败日志**  

```
INIT_REPORT Init Duration: 1236.04 ms Phase: init Status: error Error Type: Extension.Crash
```

如果 `Init` 阶段成功，除非启用 [SnapStart](snapstart.md) 或[预置并发](provisioned-concurrency.md)，否则 Lambda 不会发出 `INIT_REPORT` 日志。SnapStart 和预置并发函数始终会发出 `INIT_REPORT`。有关更多信息，请参阅 [监控 Lambda SnapStart](snapstart-monitoring.md)。

### 还原阶段（仅限 Lambda SnapStart）
<a name="runtimes-lifecycle-restore"></a>

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

#### 在 Restore 阶段失败
<a name="runtimes-lifecycle-restore-errors"></a>

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

**Example — RESTORE\$1REPORT 超时日志**  

```
RESTORE_REPORT Restore Duration: 1236.04 ms Status: timeout
```

**Example — RESTORE\$1REPORT 运行时系统钩子失败日志**  

```
RESTORE_REPORT Restore Duration: 1236.04 ms Status: error Error Type: Runtime.ExitError
```

有关 `RESTORE_REPORT` 日志的更多信息，请参阅 [监控 Lambda SnapStart](snapstart-monitoring.md)。

### 调用阶段
<a name="runtimes-lifecycle-invoke"></a>

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

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

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

### 在调用阶段失败
<a name="runtimes-lifecycle-invoke-with-errors"></a>

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

![\[执行环境示例：初始化、调用、调用时出错、调用、关闭\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/Overview-Invoke-with-Error.png)


在上图中：
+ 第一个阶段是 **INIT** 阶段，运行没有错误。
+ 第二个阶段是 **INVOKE** 阶段，运行没有错误。
+ 在某些情况下，假设您的函数遇到调用失败（常见原因包括函数超时、运行时错误、内存耗尽、VPC 连接问题、权限错误、并发限制和各种配置问题）。有关可能的调用失败的完整列表，请参阅 [Lambda 中的调用问题疑难解答](troubleshooting-invocation.md)。标签为 **INVOKE WITH ERROR** 的第三个阶段演示了这种情况。出现这种情况时，Lambda 服务会执行重置。重置的行为类似于 `Shutdown` 事件。首先，Lambda 会关闭运行时，然后向每个注册的外部扩展发送一个 `Shutdown` 事件。该事件包括关闭的原因。如果此环境用于新调用，则 Lambda 会将扩展和运行时与下一次调用一起重新初始化。

  请注意，Lambda 重置不会在下一个初始化阶段之前清除 `/tmp` 目录内容。这种行为与常规关闭阶段一致。
**注意**  
AWS 目前正在实施对 Lambda 服务的更改。由于这些更改，您可能会看到 AWS 账户 中不同 Lambda 函数发出的系统日志消息和跟踪分段的结构和内容之间存在细微差异。  
如果您的函数的系统日志配置设置为纯文本，则当您的函数遇到调用失败时，此更改会影响在 CloudWatch Logs 中捕获的日志消息。以下示例显示了新旧格式的日志输出。  
这些更改将在未来几周内实施，除中国和 GovCloud 区域外，所有 AWS 区域 的函数都将过渡到使用新格式的日志消息和跟踪分段。

    
**Example 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
  ```  
**Example 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: 3117 ms Memory Size: 128 MB Max Memory Used: 33 MB Init Duration: 111.23 ms
  ```

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

    
**Example 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: 214 ms Memory Size: 128 MB Max Memory Used: 31 MB Init Duration: 80.00 ms Status: error Error Type: Runtime.ExitError
  ```  
**Example 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: 3101 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** 持续时间 \$1 **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.md)。每当在调用阶段之间出现隐藏初始化时，Telemetry API 都会发出 `INIT_START`、`INIT_RUNTIME_DONE`、`INIT_REPORT` 事件以及 `phase=invoke`。
+ 第五个阶段是 **SHUTDOWN** 阶段，该阶段运行没有错误。

### 关闭阶段
<a name="runtimes-lifecycle-shutdown"></a>

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

**持续时间限制**：`Shutdown` 阶段的最长持续时间取决于已注册扩展的配置：
+ 0 毫秒 – 无已注册扩展的函数
+ 500 毫秒 – 带有注册的内部扩展的函数
+ 2000 毫秒 – 具有一个或多个注册的外部扩展的函数

如果运行时或扩展没有在限制范围内响应 `Shutdown` 事件，则 Lambda 会使用 `SIGKILL` 信号结束该进程。

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

当再次调用该函数时，Lambda 会解冻环境以便重复使用。重复使用执行环境会产生以下影响：
+ 在该函数的处理程序方法的外部声明的对象保持已初始化的状态，再次调用函数时提供额外的优化功能。例如，如果您的 Lambda 函数建立数据库连接，而不是重新建立连接，则在后续调用中使用原始连接。建议您在代码中添加逻辑，以便在创建新连接之前检查是否存在连接。
+ 每个执行环境都在 `/tmp` 目录中提供 512MB 到 10240MB 之间的磁盘空间（以 1MB 递增）。冻结执行环境时，目录内容会保留，同时提供可用于多次调用的暂时性缓存。您可以添加额外的代码来检查缓存中是否有您存储的数据。有关部署大小限制的更多信息，请参阅[Lambda 配额Lambda 限额](gettingstarted-limits.md)：
+ 如果 Lambda 重复使用执行环境，则由 Lambda 函数启动但在函数结束时未完成的后台进程或回调将继续执行。确保代码中的任何后台进程或回调在代码退出前已完成。

## 冷启动和延迟
<a name="cold-start-latency"></a>

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

![\[性能优化图 1\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/perf-optimize-figure-1.png)


在此图中，下载代码和设置环境的前两个步骤通常称为“冷启动”。您[需为此时间付费](https://aws.amazon.com/blogs/compute/aws-lambda-standardizes-billing-for-init-phase/)，它会增加总调用持续时间的延迟。

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

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

## 使用预调配并发减少冷启动
<a name="cold-starts-pc"></a>

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

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

![\[性能优化图 4\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/perf-optimize-figure-4.png)


## 优化静态初始化
<a name="static-initialization"></a>

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

以下 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 层。
+ 代码量和初始化工作。
+ 库和其他服务在设置连接和其他资源方面的表现。

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

重要的是，函数只导入其需要的库和依赖项。例如，如果您仅在 AWS 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)
```

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

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