

的第 4 版 (V4) 适用于 .NET 的 AWS SDK 已经发布！

有关重大更改和迁移应用程序的信息，请参阅[迁移主题](https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/net-dg-v4.html)。

 [![Orange button with text "Click here for details".](https://docs.aws.amazon.com/zh_cn/sdk-for-net/v4/developer-guide/images/BannerButton_less-round.png)](https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/net-dg-v4.html)

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

# 的性能最佳实践 适用于 .NET 的 AWS SDK
<a name="net-dg-performance"></a>

如何创建客户端、处理响应和配置应用程序对吞吐量、延迟和内存使用有很大影响。本主题介绍可帮助您的应用程序高效、可靠地运行的使用和配置模式。这些模式在高负载或资源受限的环境（例如容器和无服务器函数）中最为重要。遵循这些做法可以提高性能并防止常见问题，例如响应缓慢、挂起和内存使用率过高。

最有影响力的做法如下：
+ [重复使用单个长期服务客户端，](#net-dg-performance-reuse-clients)而不是为每个请求创建一个服务客户端。
+ [处置响应和流，](#net-dg-performance-dispose)以便将网络连接释放回连接池。
+ [`await`正确使用`async`/](#net-dg-performance-async)，切勿阻塞异步 SDK 调用。
+ [为配置 .NET 垃圾收集 AWS Lambda 还有亚马逊 ECS](#net-dg-performance-gc)为受限环境（例如 AWS Lambda 和 Amazon ECS）配置.NET 垃圾回收功能。
+ [在高吞吐量下管理 HTTP 连接](#net-dg-performance-connections)和连接限制。

在开始之前，请务必[设置环境](net-dg-config.md)并[配置项目](configuring-the-sdk.md)。

## 重复使用单个长期服务客户端
<a name="net-dg-performance-reuse-clients"></a>

诸如 [ Amazons3Client之类的服务客户端](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/TS3Client.html)[AmazonDynamoDBClient](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/DynamoDBv2/TDynamoDBClient.html)是*线程安全的*，构造*成本相对较高且*使用寿命长。* *构建客户端可以解析区域和终端节点信息，并建立底层 HTTP 基础架构。为每项服务创建一个客户端，并在应用程序的整个生命周期中重复使用。如果您致电多个区域，请为每个区域创建一个单独的客户端。

**警告**  
不要为每个请求或在循环内创建新的服务客户端。反复创建客户端会破坏底层 HTTP 基础架构，从而增加可衡量的延迟。它还会耗尽插座或手柄，导致请求失败或在负载下挂起。

在使用依赖注入的应用程序中，将客户端注册为单例。`AWSSDK.Extensions.NETCore.Setup`NuGet包中的`AddAWSService`扩展方法将客户端注册的默认生命周期为`ServiceLifetime.Singleton`。客户端是在第一次请求时创建的，并且在进程的整个生命周期中都会重复使用相同的实例。有关使用依赖注入注册 AWS 服务以及从配置中读取选项的更多信息，请参阅[AWSSDK.Extensions.NETCore.Setup 和 iconfiguration](net-dg-config-netcore.md)。

您也可以手动将客户端注册为单例。

```
builder.Services.AddSingleton<IAmazonS3>(_ => new AmazonS3Client());
```

**注意**  
因为默认情况下会将客户端`AddAWSService`注册为单例，所以不要处置它提供的客户端。如果您需要非默认生命周期，请将不同的`ServiceLifetime`值传递给的可选`lifetime`参数。`AddAWSService`

重复使用客户端**并不**意味着你应该停止处理每个操作的响应。在应用程序生命周期内重复使用客户端，但继续处置各个操作返回的响应和流，如中所述[处置响应和直播](#net-dg-performance-dispose)。

## 处置响应和直播以释放连接
<a name="net-dg-performance-dispose"></a>

一些 SDK 响应对象带有网络直播流。最常见的示例是 [ GetObjectResponse](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/TGetObjectResponse.html)，它通过其`ResponseStream`属性实现`IDisposable`和公开对象的内容。在完全读取流或处理响应之前，响应会保持开放的 HTTP 连接。如果您泄露了这些响应（例如，通过循环调用`GetObject`而不处置每个结果），则打开的连接会累积直到连接池耗尽，下一次调用就会阻塞。这是关于第 N 个对象上的下载 “随机挂起” 或停滞的报道背后的原因。

务必将直播响应打包在`using`声明中，并立即阅读或复制其直播。

```
using Amazon.S3;
using Amazon.S3.Model;

// s3Client is a reused, long-lived client.
var request = new GetObjectRequest { BucketName = bucketName, Key = key };

using var response = await s3Client.GetObjectAsync(request);
await response.WriteResponseStreamToFileAsync(filePath, append: false, CancellationToken.None);
```

处置响应和处置响应`ResponseStream`是等效的；任何一个都关闭底层网络流并将连接返回到池。

**提示**  
如果您只需要对象元数据，例如大小、上次修改时间、内容类型或 ETag，请调用`GetObjectMetadata`或`GetObjectMetadataAsync`代替。`GetObject`元数据操作发出 HTTP `HEAD` 请求且不传输任何对象正文，因此无需管理任何内容流。  

```
var metadata = await s3Client.GetObjectMetadataAsync(bucketName, key);
Console.WriteLine($"Size: {metadata.ContentLength} bytes");
```

**警告**  
响应和流类型（例如）`GetObjectResponse`不实现终结器，因此您不能依靠垃圾回收来为您释放它们的连接。您必须使用`using`或显式`Dispose`调用确定性地处理响应和流。

## async/await 正确使用
<a name="net-dg-performance-async"></a>

在现代 .NET 上，中的服务操作 适用于 .NET 的 AWS SDK 是异步的，返回 a `Task`。在 .NET Framework 上，同步方法也存在，但异步调用的扩展性更好，建议使用。使用代码执行操作`await`，并`async`传播到代码的每一层，直至入口点。有关使用 SDK 进行异步编程的更多信息，请参阅[异步编程](sdk-net-async-api.md)。

**警告**  
不要使用`.Result`、`.Wait()`或屏蔽异步 SDK 调用`.GetAwaiter().GetResult()`。这种*异步同步*模式是应用程序挂起的常见原因：  
在负载下，阻塞调用消耗线程的速度快于线程池的增长速度，因此无法连续运行。这种*线程池匮乏*表现为无限期挂起，是现代 .NET（ASP.NET Core没有默认值）上的主要故障模式。`SynchronizationContext`
某些上下文捕获`SynchronizationContext`，例如经典ASP.NET、Windows FormsWPFBlazor WebAssembly、和.NET 框架应用程序。在这些情况下，阻塞调用线程而延续需要相同的线程会导致*死锁*。SDK 中的操作在`ConfigureAwait(false)`内部使用，因此它们不会将其延续内容发布回捕获的上下文中。死锁是由调用链中其他地方的`async`代码造成的，这些代码确实捕获了死锁。自`await`始至终使用SDK调用可以完全避免问题。

以下方法阻塞异步调用，可能导致线程池死锁或耗尽。

```
// Anti-pattern: do not do this.
public GetObjectResponse Get(GetObjectRequest request)
{
    return s3Client.GetObjectAsync(request).Result;
}
```

相反，进行方法`async`和`await`调用。

```
public async Task<GetObjectResponse> GetAsync(GetObjectRequest request)
{
    return await s3Client.GetObjectAsync(request);
}
```

异步代码的其他建议：
+ 将一个传递`CancellationToken`给每个操作，这样慢速或停顿的呼叫就可以取消，而不是挂起。有关更多信息，请参阅 [使用 `CancellationToken` 参数配置超时](retries-timeouts.md#timeouts-async)。
+ 不要在空`catch`块中吞下异常。这样做会掩盖真正的失败，使挂机与错误无法区分。捕获特定的异常并记录它们。
+ 重新抛出捕获的异常时，使用`throw;`而不是 s `throw ex;` o 来保留原始堆栈跟踪。
+ 如果您必须从同步边界调用，则将其视为最后的手段，并将工作与捕获的上下文隔离开来，而不是将阻塞调用设为默认模式。

## 为配置 .NET 垃圾收集 AWS Lambda 还有亚马逊 ECS
<a name="net-dg-performance-gc"></a>

当容器出现 “内存泄漏” 时，.NET 垃圾收集器 (GC) 可能会保留回收的内存以供重复使用。因此，即使托管堆没有增长，进程内存也可能显得很高且稳定。此外，GC 不会自动检测容器的内存限制（其 cgroup 限制）。在受限的环境中，堆可能会向主机的内存而不是容器的极限增长。这可能导致`OutOfMemoryException`或容器被终止。

为了帮助 GC 在受限的环境中正常运行：
+ 在容器上设置明确的内存限制，以便 GC 遵守 cgroup 限制， and/or 设置`DOTNET_GCHeapHardLimit`（绝对字节值，十六进制）或`DOTNET_GCHeapHardLimitPercent`环境变量来限制托管堆的上限。在报告的几个 Amazon ECS 内存不足案例中，设置硬内存限制解决了崩溃问题。
+ 在小型 AWS Lambda 和 Amazon ECS 主机上，考虑禁用并发（后台）垃圾回收，这样收集器就不会预留额外内存；例如，将`DOTNET_gcConcurrent`环境变量设置为`0`，或`<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>`在项目文件中设置。
+ 绑定您的并发量。同时启动许多操作，例如调用`Task.WhenAll`大量集合，会膨胀进程内存，并可能耗尽连接池。改为限制并行度。例如，避免这种无界模式：

  ```
  // Anti-pattern: starts one task per item with no limit.
  await Task.WhenAll(keys.Select(key => s3Client.GetObjectMetadataAsync(bucket, key)));
  ```

  取而代之的是，使用`Parallel.ForEachAsync`以下方法限制并发量：

  ```
  var options = new ParallelOptions { MaxDegreeOfParallelism = 10 };
  await Parallel.ForEachAsync(keys, options, async (key, token) =>
  {
      await s3Client.GetObjectMetadataAsync(bucket, key, token);
  });
  ```

有关这些设置的更多信息，请参阅 learn.microsoft.com [ 上的垃圾收集](https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector)运行时配置选项。有关 AWS 计算的特定指导，请参阅 AWS 开发者工具博客文章[为 Amazon ECS 配置.NET 垃圾回收和 AWS Lambda](https://aws.amazon.com/blogs/developer/configuring-net-garbage-collection-for-amazon-ecs-and-aws-lambda)。

## 管理 HTTP 连接和连接限制
<a name="net-dg-performance-connections"></a>

在高吞吐量下，两个与连接相关的问题很常见。首先是打开了太多短暂的连接。这会耗尽临时端口，留下套接字，并增加 TCP 和 TLS 握手延迟。`TIME_WAIT`第二个问题是可用的连接太少，这给并行性带来了瓶颈。重复使用单个长寿命客户端（参见[重复使用服务客户端](#net-dg-performance-reuse-clients)）是健康连接池的基础，因为池化取决于客户端的重复使用。

要调整每个端点的并发连接数，请在客户端配置上设置该`MaxConnectionsPerServer`属性。当此属性为`null`（默认值）时，基础`HttpClientHandler`默认值适用，这在现代 .NET 中实际上是无限的。仅当对同一端点的多个并发请求出现连接瓶颈时才提高。一个不错的起点是每个终端节点的预期并发请求的峰值数。在不提高吞吐量的情况下，将其设置得远高于工作负载需求会浪费套接字。

```
using Amazon.S3;

var config = new AmazonS3Config
{
    MaxConnectionsPerServer = 50
};

var s3Client = new AmazonS3Client(config);
```

如果您已经使用依赖注入，请通过配置客户端`AWSSDK.Extensions.NETCore.Setup`。当您使用 DI 或注册多个服务客户端时，推荐使用这种方法。它可以集中配置，使客户端易于注入和测试。您可以从应用程序的配置中设置配置值，而不是在代码中设置配置值。有关更多信息，请参阅 [AWSSDK.Extensions.NETCore.Setup 和 iconfiguration](net-dg-config-netcore.md)。

最后，绑定自己的并行度，这样启动的并发操作就不会超过连接限制允许的次数。例如，根据您的连接限制`SemaphoreSlim`进行门禁呼叫：

```
var throttle = new SemaphoreSlim(50); // match MaxConnectionsPerServer
await throttle.WaitAsync(token);
try
{
    await s3Client.GetObjectAsync(request, token);
}
finally
{
    throttle.Release();
}
```

## 配置超时和重试
<a name="net-dg-performance-timeouts"></a>

超时和重试直接影响感知性能。`Timeout`值过高会使停滞的请求长时间阻塞。当服务已经返回限制错误时，激进的重试策略会增加更多请求，并可能使限制变得更糟。选择符合应用程序对延迟和失败容忍度的重试策略，让真正的异常传播，而不是以掩盖挂起的方式重试。

**注意**  
该`Timeout`属性不影响异步调用。如果您使用异步调用，请[使用 `CancellationToken` 参数配置超时](retries-timeouts.md#timeouts-async)改为参见。

有关重试模式和`Timeout`属性（仅`ReadWriteTimeout`适用于.NET Framework）的更多信息以及如何设置它们的示例，请参阅[重试和超时](retries-timeouts.md)。`MaxErrorRetry`

## 优化串流和大对象传输 (Amazon S3)
<a name="net-dg-performance-streaming"></a>

要上传和下载大型对象或许多对象，请使用`Amazon.S3.Transfer`命名空间中的[TransferUtility](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/TTransferUtility.html)类。它使用分段传输并行上传和下载，并为您管理流、分段和连接。这比单流传输更快，也是移动大型对象的推荐方式。
+ **并行分段下载。**SDK 的早期版本以单个流的形式下载对象，而不是并行下载各个部分。从`AWSSDK.S3`版本 4.0.17 开始，通过、和方法`TransferUtility`提供分段（并行）下载。`DownloadWithResponseAsync` `OpenStreamWithResponseAsync` `DownloadDirectoryWithResponseAsync`使用时`OpenStreamWithResponseAsync`，当您消耗返回的流时，对象的各个部分会缓冲在内存中。使用的`MaxInMemoryParts`属性控制缓冲了多少个部件。[ TransferUtilityOpenStreamRequest ](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/TTransferUtilityOpenStreamRequest.html)对于大多数转账，首选`TransferUtility`。仅在需要特定范围或自定义并行[GetObjectRequest](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/TGetObjectRequest.html)模式时，才使用`ByteRange`属性的方式自行下载字节范围。有关更多信息，请参阅 AWS 开发者工具博客[上](https://aws.amazon.com/blogs/developer/introducing-multipart-download-support-for-aws-sdk-for-net-transfer-manager/)介绍适用于.NET Transfer Manager 的 AWS SDK 的分段下载支持。
+ **上传的内容长度。**Amazon S3 `PUT` 需要已知的内容长度，默认情况下，开发工具包会在请求正文中计算校验和。当已知长度且可搜索流时，SDK 无需在内存中缓冲整个对象即可执行此操作。通过根据需要进行缓冲来为您`TransferUtility`处理不可搜索的流。如果您改为使用不可搜索的流（例如中的原始请求正文ASP.NET Core）`PutObjectAsync`直接调用，则请求可能会失败。提供可搜索的直播，明确设置内容长度，或配置 SDK 计算校验和的方式。有关更多信息，请参阅《软件开发工具包和 AWS 工具参考指南》[中的](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)数据完整性保护。
+ **零件尺寸。**Amazon S3 允许每次分段上传最多 10,000 个分段。当总长度已知时，SDK 会自动计算出保持在该限制范围内的分段大小。你主要需要为`PartSize`事先不知道长度的直播做好准备。否则，较小的默认分段可能会超过超大上传的限制。更大的部件大小还可以减少每个部件的开销，但代价是每个部件需要更多的内存。

## 诊断性能问题
<a name="net-dg-performance-diagnose"></a>

当您调查减速、挂起或明显泄漏时，以下信号可帮助您快速找到原因：
+ 越来越多的`TIME_WAIT`处于`CLOSE_WAIT`或状态（可见`netstat`）的套接字是未处理的响应或每个请求创建和丢弃的客户端的指纹。请参阅[处置响应和直播](#net-dg-performance-dispose)和[重复使用服务客户端](#net-dg-performance-reuse-clients)。
+ 启用请求指标和响应日志，以确认请求实际正在分发并测量延迟。在创建服务客户端[之前，在 ](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/Amazon/TAWSConfigs.html) AWSConfigs 上设置[LoggingConfig](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/Util/TLoggingConfig.html)对象的属性。客户端捕获设置，例如`LogMetrics`在构造时捕获设置。

  ```
  using Amazon;
  
  AWSConfigs.LoggingConfig.LogMetrics = true;
  AWSConfigs.LoggingConfig.LogResponses = ResponseLoggingOption.OnError;
  ```
+ 研究内存时，将全进程内存与托管堆区分开。高但稳定的进程内存通常是 GC 保存回收的内存，而不是泄漏。请参阅[配置垃圾收集](#net-dg-performance-gc)。