

# Lambda 托管实例的 Node.js 运行时
<a name="lambda-managed-instances-nodejs-runtime"></a>

对于 Node.js 运行时而言，Lambda 托管实例采用基于 `async`/`await` 的执行的工作线程来处理并发请求。函数初始化会在每个工作线程中发生一次。并发调用在两个维度上得到处理：工作线程在各个 vCPU 上实现并行处理，而异步执行则在每个线程内部实现并发。由同一个工作线程处理的每个并发请求都共享同一个处理程序对象和全局状态，因此需要在多个并发请求下进行安全处理。

## 最大并发数
<a name="lambda-managed-instances-nodejs-max-concurrency"></a>

Lambda 向每个执行环境发送的最大并发请求数由函数配置中的 `PerExecutionEnvironmentMaxConcurrency` 设置控制。这是一项可选设置，其默认值因运行时而异。对于 Node.js 运行时而言，其默认设置为每个 vCPU 64 个并发请求，或者您也可以自行配置其他数值。Lambda 会根据每个执行环境吸收这些请求的容量，自动调整并发请求的数量，最高到配置的最大值。

对于 Node.js，每个执行环境能够处理的并发请求数量取决于工作线程的数量以及每个工作线程异步处理并发请求的能力。工作线程的默认数量由可用的 vCPU 数量决定，或者您可以通过设置 `AWS_LAMBDA_NODEJS_WORKER_COUNT` 环境变量来配置工作线程的数量。我们建议使用异步函数处理程序，因为这样可以允许每个工作线程处理多个请求。如果您的函数处理程序是同步的，则每个工作线程一次只能处理一个请求。

## 为多并发构建函数
<a name="lambda-managed-instances-nodejs-building"></a>

通过异步函数处理程序，每个运行时工作进程能够同时处理多个请求。全局对象将在多个并发请求之间进行共享。对于可变对象，请避免使用全局状态或使用 `AsyncLocalStorage`。

AWS SDK 客户端是异步安全的，且不需要特殊处理。

**示例：全局状态**

以下代码使用了一个在函数处理程序内部发生突变的全局对象。该对象不具备异步安全性。

```
let state = {
    currentUser: null,
    requestData: null
};

export const handler = async (event, context) => {
    state.currentUser = event.userId;
    state.requestData = event.data;

    await processData(state.requestData);

    // state.currentUser might now belong to a different request
    return { user: state.currentUser };
};
```

在函数处理程序内初始化 `state` 对象可以避免共享全局状态。

```
export const handler = async (event, context) => {
    let state = {
        currentUser: event.userId,
        requestData: event.data
    };
    
    await processData(state.requestData);

    return { user: state.currentUser };
};
```

**示例：数据库连接**

以下代码使用一个共享的客户端对象，该对象在多个调用之间共享。根据使用的连接库，这可能并不具备并发安全性。

```
const { Client } = require('pg');

// Single connection created at init time
const client = new Client({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD
});

// Connect once during cold start
client.connect();

exports.handler = async (event) => {
  // Multiple parallel invocations share this single connection = BAD
  // With multi-concurrent Lambda, queries will collide
  const result = await client.query('SELECT * FROM users WHERE id = $1', [event.userId]);
  
  return {
    statusCode: 200,
    body: JSON.stringify(result.rows[0])
  };
};
```

并发安全的方法是使用连接池。该池对每个并发数据库查询使用单独的连接。

```
const { Pool } = require('pg');

// Connection pool created at init time
const pool = new Pool({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  max: 20,  // Max connections in pool
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000
});

exports.handler = async (event) => {
  // Pool gives each parallel invocation its own connection
  const result = await pool.query('SELECT * FROM users WHERE id = $1', [event.userId]);
  
  return {
    statusCode: 200,
    body: JSON.stringify(result.rows[0])
  };
};
```

## Node.js 22 基于回调的处理程序
<a name="lambda-managed-instances-nodejs-callback-handlers"></a>

使用 Node.js 22 时，您不能对 Lambda 托管实例使用基于回调的函数处理程序。只有 Lambda（默认）函数才支持基于回调的处理程序。对于 Node.js 24 及更高版本的运行时，基于回调的函数处理程序在 Lambda（默认）和 Lambda 托管实例中均已不受支持。

在使用 Lambda 托管实例时，请改用 `async` 函数处理程序。有关更多信息，请参阅[在 Node.js 中的定义 Lambda 函数处理程序](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html)。

## 共享的 /tmp 目录
<a name="lambda-managed-instances-nodejs-shared-tmp"></a>

`/tmp` 目录在执行环境中为所有并发请求共享使用。对同一个文件进行并发写入可能会导致数据损坏，例如，如果另一个进程覆盖了该文件。要解决这个问题，要么为共享文件实施文件锁定机制，要么根据每次请求使用唯一的文件名以避免冲突。记得清理不再需要的文件，以免耗尽可用空间。

## 日志记录
<a name="lambda-managed-instances-nodejs-logging"></a>

在多并发系统中，日志交错（即来自不同请求的日志条目在日志中交错排列）是常见现象。使用 Lambda 托管实例的函数始终使用[高级日志记录控制](monitoring-logs.md#monitoring-cloudwatchlogs-advanced)引入的结构化 JSON 日志格式。此格式包括 `requestId`，使得日志条目能够与单个请求相关联。当您使用 `console` 记录器时，`requestId` 会自动包含在每个日志条目中。有关更多信息，请参阅 [在 Node.js 中使用 Lambda 高级日志记录控件](nodejs-logging.md#node-js-logging-advanced)。

常见的第三方日志库（例如 [Winston](https://github.com/winstonjs/winston)）通常支持通过控制台进行日志输出。

## 请求上下文
<a name="lambda-managed-instances-nodejs-request-context"></a>

使用 `context.awsRequestId` 将提供对当前请求的请求 ID 的异步安全性访问权限。

使用 `context.xRayTraceId` 访问 X-Ray 跟踪 ID。这为当前请求的跟踪 ID 提供了并发安全的访问权限。Lambda 不支持将 `_X_AMZN_TRACE_ID` 环境变量用于 Lambda 托管实例。使用 AWS SDK 时，X-Ray 跟踪 ID 会自动传播。

使用 `context.getRemainingTimeInMillis()` 检测超时。请参阅[错误处理和恢复](lambda-managed-instances-execution-environment.md#lambda-managed-instances-error-handling)了解更多信息。

## 初始化和关闭
<a name="lambda-managed-instances-nodejs-init-shutdown"></a>

函数初始化会在每个工作线程中发生一次。如果您的函数在初始化过程中发出日志，您可能会看到重复的日志条目。

对于带有扩展程序的 Lambda 函数，其执行环境在关闭时会发出一个 SIGTERM 信号。扩展程序使用此信号来触发清理任务，例如刷新缓冲区。带有扩展程序的 Lambda（默认）函数也可以使用 `process.on()` 订阅 SIGTERM 信号。使用 Lambda 托管实例的函数不支持此功能，因为 `process.on()` 不能与工作线程一起使用。要了解有关执行环境生命周期的更多信息，请参阅 [了解 Lambda 执行环境生命周期](lambda-runtime-environment.md)。

## 依赖项版本
<a name="lambda-managed-instances-nodejs-dependencies"></a>

Lambda 托管实例需要以下最低程序包版本：
+ AWS SDK for JavaScript v3：版本 3.933.0 或更高版本
+ AWS X-Ray SDK for Node.js：版本 3.12.0 或更高版本
+ 适用于 OpenTelemetry 的 AWS Distro - 适用于 JavaScript 的检测工具：版本 0.8.0 或更高版本
+ 适用于 AWS Lambda 的 Powertools（TypeScript）：版本 2.29.0 或更高版本

## Powertools for AWS Lambda (TypeScript)
<a name="lambda-managed-instances-nodejs-powertools"></a>

适用于 AWS Lambda 的 Powertools（TypeScript）与 Lambda 托管实例兼容，并提供用于日志记录、跟踪、指标等的实用工具。有关更多信息，请参阅[适用于 AWS Lambda 的 Powertools（TypeScript）](https://github.com/aws-powertools/powertools-lambda-typescript)。

## 后续步骤
<a name="lambda-managed-instances-nodejs-next-steps"></a>
+ 查看 [Lambda 托管实例的 Java 运行时](lambda-managed-instances-java-runtime.md)
+ 查看 [Lambda 托管实例的 Python 运行时](lambda-managed-instances-python-runtime.md)
+ 查看 [Lambda 托管实例的 .NET 运行时](lambda-managed-instances-dotnet-runtime.md)
+ 了解有关[扩展 Lambda 托管实例](lambda-managed-instances-scaling.md)的信息