

# Node.js 和 DAX
<a name="DAX.client.run-application-nodejs-3"></a>

# Node.js 的默认客户端配置
<a name="DAX-client-config-JS"></a>

配置 DAX JavaScript SDK 客户端时，您可以自定义各种参数来优化性能、连接处理和错误恢复能力。下表概述了控制您的客户端与 DAX 集群交互方式的默认配置设置，包括超时值、重试机制、凭证管理和运行状况监控选项。有关更多信息，请参阅 [DynamoDBClient Operations](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/)。


**DAX JS SDK 客户端默认值**  

| 参数 | 类型 | 说明 | 
| --- | --- | --- | 
|  `region` optional  |  `string`  |  要用于 DAX 客户端的 AWS 区域（示例：“us-east-1”）。如果未通过环境变量提供，则这是必需的参数。  | 
|  `endpoint` 必需  |  `string`  | SDK 连接到的集群的端点。 示例： 未加密：dax-cluster-name.region.amazonaws.com 已加密：daxs://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com  | 
|  `requestTimeout` 默认值 6000 毫秒  |  `number`  | 这定义了客户端等待来自 DAX 的响应的最长时间。  | 
|  `writeRetries` 默认值 1  |  `number`  | 尝试对失败的写入请求进行重试的次数。  | 
|  `readRetries` 默认值 1  |  `number`  | 尝试对失败的读取请求进行重试的次数。  | 
|  `maxRetries` 默认值 1  |  `number`  | 尝试对失败的请求进行重试的最大次数。 如果设置了 readRetries/writeRetries，则在 readRetries 和 writeRetries 中设置的配置优先于 maxRetries。  | 
|  `connectTimeout` 默认值 10000 毫秒  |  `number`  | 与任何集群节点建立连接的超时（以毫秒为单位）。  | 
|  `maxConcurrentConnections` 默认值 100  |  `number`  | 限制客户端实例可以在 DAX 集群中对于每个节点创建的并发连接总数。  | 
|  `maxRetryDelay` 默认值 7000 毫秒  |  `number`  | 当 DAX 服务器通过将 `waitForRecoveryBeforeRetrying` 标志设置为 true 来指示需要恢复时，客户端将在尝试重试之前暂停。在这些恢复时段内，`maxRetryDelay` 参数决定了两次重试之间的最大等待时间。这一特定于恢复的配置仅在 DAX 服务器处于恢复模式时才适用。对于所有其它场景，重试行为遵循以下两种模式之一：要么是基于重试计数（由 `writeRetries`、`readRetries` 或 `maxRetries` 参数控制）的指数延迟，要么是根据异常类型立即重试。  | 
|  `credentials` optional  |  `[AwsCredentialIdentity](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)` \$1 `[AwsCredentialIdentityProvider](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)`  |  要用于对请求进行身份验证的 AWS 凭证。这可以作为 AwsCredentialIdentity 或 AwsCredentialIdentityProvider 提供。如果未提供，AWS SDK 将自动使用默认凭证提供程序链。示例：`\$1 accessKeyId: 'AKIA...', secretAccessKey: '...', sessionToken: '...' \$1` \$1 @default 使用默认的 AWS 凭证提供程序链。  | 
|  `healthCheckInterval` 默认值 5000 毫秒  |  `number`  | 集群运行状况检查之间的间隔（以毫秒为单位）。间隔越短，检查频率越高。  | 
|  `healthCheckTimeout` 默认值 1000 毫秒  |  `number`  | 运行状况检查完成的超时（以毫秒为单位）。  | 
|  `skipHostnameVerification` 默认值 false  |  `boolean`  |  跳过 TLS 连接的主机名验证。这对未加密的集群没有影响。默认设置为执行主机名验证，将其设置为 True 将跳过验证。请务必理解将其关闭的含义，即无法对您正在连接的集群进行身份验证。  | 
|  `unhealthyConsecutiveErrorCount` 默认值 5  |  `number`  | 设置在运行状况检查间隔内发出节点运行状况不正常信号所需的连续错误数。  | 
|  `clusterUpdateInterval` 默认值 4000 毫秒  |  `number`  | 返回对集群成员进行轮询以了解成员资格变更之间的间隔。  | 
|  `clusterUpdateThreshold` 默认值 125  |  `number`  | 返回一个阈值，低于该阈值将不会对集群进行轮询来了解成员资格变更。  | 
|  `credentailProvider` 可选 \$1 默认值 null  |  `[AwsCredentialIdentityProvider](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)`  | 用于对 DAX 请求进行身份验证的 AWS 凭证的用户定义提供程序。  | 


**DaxDocument 的分页配置**  

| 名称 | 类型 | Detail | 
| --- | --- | --- | 
|  `client`  |  DaxDocument  |  DaxDocument 类型的实例。  | 
|  `pageSize`  |  数字  |  确定每页项目数。  | 
|  `startingToken` 可选  |  any  |  先前响应中的 LastEvaluatedKey 可用于后续请求。  | 

有关分页的用法，请参阅 [TryDax.js](DAX.client.tutorial-TryDax.md)。

# 迁移到 DAX Node.js SDK V3
<a name="DAX.client.run-application-nodejs-3-migrating"></a>

本迁移指南将有助于您转换现有的 DAX Node.js 应用程序。新的 SDK 需要 Node.js 18 或更高版本，并在如何构建 DynamoDB Accelerator 代码方面引入了几项重要变化。本指南将引导您了解主要区别，包括语法更改、新的导入方法和更新的异步编程模式。

## V2 Node.js DAX 用法
<a name="DAX.client.run-application-nodejs-3-migrating-V2-usage"></a>

```
const AmazonDaxClient = require('amazon-dax-client');
const AWS = require('aws-sdk');

var region = "us-west-2";

AWS.config.update({
  region: region,
});

var client = new AWS.DynamoDB.DocumentClient();

if (process.argv.length > 2) {
  var dax = new AmazonDaxClient({
    endpoints: [process.argv[2]],
    region: region,
  });
  client = new AWS.DynamoDB.DocumentClient({ service: dax });
}

// Make Get Call using Dax
var params = {
    TableName: 'TryDaxTable',
    pk: 1,
    sk: 1
}
client.get(params, function (err, data) {
    if (err) {
        console.error(
            "Unable to read item. Error JSON:",
            JSON.stringify(err, null, 2)
          );
    } else {
        console.log(data);
    }
});
```

## V3 Node.js DAX 用法
<a name="DAX.client.run-application-nodejs-3-migrating-V3-dax-usage"></a>

对于使用 DAX Node.js V3，Node 版本 18 或更高版本是首选版本。要移到 Node 18，请使用以下命令：

```
import { DaxDocument } from '@amazon-dax-sdk/lib-dax';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

let client: DynamoDBDocument | DaxDocument = DynamoDBDocument.from(
  new DynamoDBClient({ region: 'us-west-2' })
);

if (process.argv.length > 2) {
  client = new DaxDocument({
    endpoints: [process.argv[2]],
    region: 'us-west-2',
  });
}

const params = {
  TableName: 'TryDaxTable',
  Key: { pk: 1, sk: 1 },
};

try {
  const results = await client.get(params);
  console.log(results);
} catch (err) {
  console.error(err);
}
```

适用于 Node.js 的 DAX SDK v3.x 与 [AWS SDK for Node.js v3.x](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/) 兼容。适用于 Node.js 的 DAX SDK v3.x 支持使用 [aggregated](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/#high-level-concepts) 客户端。请注意，DAX 不支持创建基本客户端。有关不支持的功能的更多详细信息，请参阅[与 AWS SDK V3 不对等的功能](#DAX.client.run-application-nodejs-3-not-in-parity)。

执行这些步骤可在 Amazon EC2 实例上运行 Node.js 示例应用程序。

**运行 DAX 的 Node.js 示例**

1. 在 Amazon EC2 实例上设置 Node.js，如下所示：

   1. 安装节点版本管理器 (`nvm`)。

      ```
      curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
      ```

   1. 使用 nvm 安装 Node.js。

      ```
      nvm install 18
      ```

   1. 通过 nvm 使用 Node 18

      ```
      nvm use 18
      ```

   1. 测试 Node.js 已安装且运行正常。

      ```
      node -e "console.log('Running Node.js ' + process.version)"
      ```

      这应该显示以下消息。

      `Running Node.js v18.x.x`

1. 使用节点程序包管理器 (`npm`) 安装 DaxDocument Node.js 客户端。

   ```
   npm install @amazon-dax-sdk/lib-dax
   ```

## TryDax 示例代码
<a name="DAX.client.run-application-nodejs-3-TryDax-sample-code"></a>

要评估 DynamoDB Accelerator（DAX）的性能优势，请按照以下步骤运行一个示例测试，来比较标准 DynamoDB 和 DAX 集群之间的读取操作时间。

1. 设置工作区并将 `lib-dax` 安装为依赖项后，将 [TryDax.js](DAX.client.tutorial-TryDax.md) 复制到项目。

1. 对您的 DAX 集群运行该程序。要确定 DAX 集群的端点，请选择下列选项之一：
   +  **使用 DynamoDB 控制台** — 选择 DAX 集群。集群端点显示在控制台中，如下面的示例所示。

     ```
     dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com
     ```
   + **使用 AWS CLI** — 输入下面的命令。

     ```
     aws dax describe-clusters --query "Clusters[*].ClusterDiscoveryEndpoint"
     ```

     集群端点显示在输出中，如下面的示例所示。

     ```
     {
         "Address": "my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com",
         "Port": 8111,
         "URL": "dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com"
     }
     ```

1. 现在，通过将集群端点指定为命令行参数来运行该程序。

   ```
   node TryDax.js dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com
   ```

   您应该可以看到类似于如下所示的输出内容：

   ```
   Attempting to create table; please wait...
   Successfully created table. Table status: ACTIVE
   Writing data to the table...
   Writing 20 items for partition key:  1
   Writing 20 items for partition key:  2
   Writing 20 items for partition key:  3
   ...
   Running GetItem Test
           Total time: 153555.10 µs - Avg time: 383.89 µs
           Total time: 44679.96 µs - Avg time: 111.70 µs
           Total time: 36885.86 µs - Avg time: 92.21 µs
           Total time: 32467.25 µs - Avg time: 81.17 µs
           Total time: 32202.60 µs - Avg time: 80.51 µs
   Running Query Test
           Total time: 14869.25 µs - Avg time: 2973.85 µs
           Total time: 3036.31 µs - Avg time: 607.26 µs
           Total time: 2468.92 µs - Avg time: 493.78 µs
           Total time: 2062.53 µs - Avg time: 412.51 µs
           Total time: 2178.22 µs - Avg time: 435.64 µs
   Running Scan Test
           Total time: 2395.88 µs - Avg time: 479.18 µs
           Total time: 2207.16 µs - Avg time: 441.43 µs
           Total time: 2443.14 µs - Avg time: 488.63 µs
           Total time: 2038.24 µs - Avg time: 407.65 µs
           Total time: 1972.17 µs - Avg time: 394.43 µs
   Running Pagination Test
   Scan Pagination
   [
     { pk: 1, sk: 1, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 2, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 3, someData: 'XXXXXXXXXX' }
   ]
   [
     { pk: 1, sk: 4, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 5, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 6, someData: 'XXXXXXXXXX' }
   ]
   ...
   Query Pagination
   [
     { pk: 1, sk: 1, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 2, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 3, someData: 'XXXXXXXXXX' }
   ]
   [
     { pk: 1, sk: 4, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 5, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 6, someData: 'XXXXXXXXXX' }
   ]
   ...
   Attempting to delete table; please wait...
   Successfully deleted table.
   ```

   记下时间信息。`GetItem`、`Query`、`Scan` 测试所需的微秒数。

1. 在这种情况下，您对 DAX 集群运行了程序。现在，您将再次运行该程序，这次是针对 DynamoDB。

1. 现在再次运行该程序，但这一次不需要将集群端点作为命令行参数。

   ```
   node TryDax.js
   ```

   查看输出，并记下计时信息。与使用 DynamoDB 相比，使用 DAX 时，`GetItem`、`Query` 和 `Scan` 所耗时间应明显更短。

## 与 AWS SDK V3 不对等的功能
<a name="DAX.client.run-application-nodejs-3-not-in-parity"></a>
+ [Bare-bones](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/#high-level-concepts) 客户端：Dax Node.js V3 不支持基本客户端。

  ```
  const dynamoDBClient = new DynamoDBClient({ region: 'us-west-2' });
  const regularParams = {
      TableName: 'TryDaxTable',
      Key: {
          pk: 1,
          sk: 1
      }
  };
  // The DynamoDB client supports the send operation.
  const dynamoResult = await dynamoDBClient.send(new GetCommand(regularParams));
  
  // However, the DaxDocument client does not support the send operation.
  const daxClient = new DaxDocument({
      endpoints: ['your-dax-endpoint'],
      region: 'us-west-2',
  });
  
  const params = {
      TableName: 'TryDaxTable',
      Key: {
          pk: 1,
          sk: 1
      }
  };
  
  // This will throw an error - send operation is not supported for DAX. Please refer to documentation.
  const result = await daxClient.send(new GetCommand(params));
  console.log(result);
  ```
+ [Middleware Stack](https://aws.amazon.com/blogs/developer/middleware-stack-modular-aws-sdk-js/)：Dax Node.js V3 不支持使用中间件函数。

  ```
  const dynamoDBClient = new DynamoDBClient({ region: 'us-west-2' });
  // The DynamoDB client supports the middlewareStack.
  dynamoDBClient.middlewareStack.add(
    (next, context) =>> async (args) => {
      console.log("Before operation:", args);
      const result = await next(args);
      console.log("After operation:", result);
      return result;
    },
    {
      step: "initialize", // or "build", "finalizeRequest", "deserialize"
      name: "loggingMiddleware",
    }
  );
  
  // However, the DaxDocument client does not support the middlewareStack.
  const daxClient = new DaxDocument({
      endpoints: ['your-dax-endpoint'],
      region: 'us-west-2',
  });
  
  // This will throw an error - custom middleware and middlewareStacks are not supported for DAX. Please refer to documentation.
  daxClient.middlewareStack.add(
    (next, context) => async (args) => {
      console.log("Before operation:", args);
      const result = await next(args);
      console.log("After operation:", result);
      return result;
    },
    {
      step: "initialize", // or "build", "finalizeRequest", "deserialize"
      name: "loggingMiddleware",
    }
  );
  ```

# TryDax.js
<a name="DAX.client.tutorial-TryDax"></a>

```
import { DynamoDB, waitUntilTableExists, waitUntilTableNotExists } from "@aws-sdk/client-dynamodb";
import { DaxDocument, daxPaginateScan, daxPaginateQuery } from "@amazon-dax-sdk/lib-dax";
import { DynamoDBDocument, paginateQuery, paginateScan } from "@aws-sdk/lib-dynamodb";

const region = "us-east-1";
const tableName = "TryDaxTable";

// Determine the client (DynamoDB or DAX)
let client = DynamoDBDocument.from(new DynamoDB({ region }));
if (process.argv.length > 2) {
  client = new DaxDocument({ region, endpoint: process.argv[2] });
}

// Function to create table
async function createTable() {
  const dynamodb = new DynamoDB({ region });
  const params = {
    TableName: tableName,
    KeySchema: [
      { AttributeName: "pk", KeyType: "HASH" },
      { AttributeName: "sk", KeyType: "RANGE" },
    ],
    AttributeDefinitions: [
      { AttributeName: "pk", AttributeType: "N" },
      { AttributeName: "sk", AttributeType: "N" },
    ],
    ProvisionedThroughput: { ReadCapacityUnits: 25, WriteCapacityUnits: 25 },
  };

  try {
    console.log("Attempting to create table; please wait...");
    await dynamodb.createTable(params);
    await waitUntilTableExists({ client: dynamodb, maxWaitTime: 30 }, { TableName: tableName });
    console.log("Successfully created table. Table status: ACTIVE");
  } catch (err) {
    console.error("Error in table creation:", err);
  }
}

// Function to insert data
async function writeData() {
  console.log("Writing data to the table...");
  const someData = "X".repeat(10);
  for (let ipk = 1; ipk <= 20; ipk++) {
    console.log("Writing 20 items for partition key: ", ipk)
    for (let isk = 1; isk <= 20; isk++) {
      try {
        await client.put({ TableName: tableName, Item: { pk: ipk, sk: isk, someData } });
      } catch (err) {
        console.error("Error inserting data:", err);
      }
    }
  }
}

// Function to test GetItem
async function getItemTest() {
  console.log("Running GetItem Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let ipk = 1; ipk <= 20; ipk++) {
      for (let isk = 1; isk <= 20; isk++) {
        promises.push(client.get({ TableName: tableName, Key: { pk: ipk, sk: isk } }));
      }
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 400).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Query
async function queryTest() {
  console.log("Running Query Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let pk = 1; pk <= 5; pk++) {
      const params = {
        TableName: tableName,
        KeyConditionExpression: "pk = :pkval and sk between :skval1 and :skval2",
        ExpressionAttributeValues: { ":pkval": pk, ":skval1": 1, ":skval2": 2 },
      };
      promises.push(client.query(params));
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 5).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Scan
async function scanTest() {
  console.log("Running Scan Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let pk = 1; pk <= 5; pk++) {
      const params = {
        TableName: tableName,
        FilterExpression: "pk = :pkval and sk between :skval1 and :skval2",
        ExpressionAttributeValues: { ":pkval": pk, ":skval1": 1, ":skval2": 2 },
      };
      promises.push(client.scan(params));
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 5).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Pagination
async function paginationTest() {
  console.log("Running Pagination Test");
  console.log("Scan Pagination");
  const scanParams = { TableName: tableName };
  const paginator = process.argv.length > 2 ? daxPaginateScan : paginateScan;
  for await (const page of paginator({ client, pageSize: 3 }, scanParams)) {
    console.log(page.Items);
  }

  console.log("Query Pagination");
  const queryParams = {
    TableName: tableName,
    KeyConditionExpression: "pk = :pkval and sk between :skval1 and :skval2",
    ExpressionAttributeValues: { ":pkval": 1, ":skval1": 1, ":skval2": 10 },
  };
  const queryPaginator = process.argv.length > 2 ? daxPaginateQuery : paginateQuery;
  for await (const page of queryPaginator({ client, pageSize: 3 }, queryParams)) {
    console.log(page.Items);
  }
}

// Function to delete the table
async function deleteTable() {
  const dynamodb = new DynamoDB({ region });
  console.log("Attempting to delete table; please wait...")
  try {
    await dynamodb.deleteTable({ TableName: tableName });
    await waitUntilTableNotExists({ client: dynamodb, maxWaitTime: 30 }, { TableName: tableName });
    console.log("Successfully deleted table.");
  } catch (err) {
    console.error("Error deleting table:", err);
  }
}

// Execute functions sequentially
(async function () {
  await createTable();
  await writeData();
  await getItemTest();
  await queryTest();
  await scanTest();
  await paginationTest();
  await deleteTable();
})();
```