Azure Cosmos DB .NET SDK 的最佳做法

适用范围: NoSQL

本文演示使用 Azure Cosmos DB .NET SDK 的最佳做法。 遵循这些做法将有助于改善延迟、提高可用性并提升整体性能。

清单

已选中 主题 详细信息/链接
SDK 版本 始终使用提供的最新版本 Azure Cosmos DB SDK,以便获取最佳性能。
单一实例客户端 在应用程序的生存期内使用 CosmosClient单个实例,以获得更好的性能
区域 确保在与 Azure Cosmos DB 帐户相同的 Azure 区域中运行应用程序,以尽可能减少延迟。 启用 2-4 个区域并在多个区域中复制帐户以获得最佳可用性。 对于生产工作负载,请启用服务托管故障转移。 如果没有此配置,则在写入区域服务中断的整个持续时间内,帐户将处于写入可用性缺失的状态,因为缺少区域连接时,手动故障转移将不会成功。 若要了解如何使用 .NET SDK 来添加多个区域,请访问此处
可用性和故障转移 在 v3 SDK 中设置 ApplicationPreferredRegionsApplicationRegion,使用首选区域列表在 v2 SDK 中设置 PreferredLocations。 在故障转移期间,系统会将写入操作发送到当前写入区域,并将所有读取操作发送到首选区域列表中的第一个区域。 有关区域故障转移机制的详细信息,请参阅可用性问题排查指南
CPU 由于客户端计算机上的资源不足,你可能会遇到连接/可用性问题。 监视运行 Azure Cosmos DB 客户端的节点上的 CPU 利用率,并在使用率非常高的情况下纵向/横向扩展。
Hosting 尽可能使用 Windows 64 位主机处理以获得最佳性能。 对于直接模式延迟敏感型生产工作负载,强烈建议尽可能使用至少 4 核心和 8 GB 内存的 VM。
连接模式 使用直接模式以获得最佳性能。 有关如何执行此操作的说明,请参阅 V3 SDK 文档V2 SDK 文档
网络 如果使用虚拟机来运行应用程序,请在 VM 上启用加速网络,以帮助解决流量较大所致的瓶颈问题,并减少延迟或 CPU 抖动。 你可能还需要考虑使用最大 CPU 使用率低于 70% 的更高端虚拟机。
临时端口耗尽 对于稀疏或偶发性连接,将 IdleConnectionTimeoutPortReuseMode 设置为 PrivatePortPoolIdleConnectionTimeout 属性可帮助控制未使用连接关闭前的时间。 这可减少未使用的连接数量。 默认情况下,空闲连接会无限期地保持为打开状态。 设置的值必须大于或等于 10 分钟。 建议值介于 20 到 24 小时之间。 PortReuseMode 属性使 SDK 可以针对各种 Azure Cosmos DB 目标终结点使用一小部分临时端口。
使用 Async/Await 避免阻止调用:Task.ResultTask.WaitTask.GetAwaiter().GetResult()。 为了获益于 async/await 模式,整个调用堆栈都是异步的。 许多同步阻止调用都会导致线程池饥饿和响应时间降低。
端到端超时 要获取端到端超时,需要同时使用 RequestTimeoutCancellationToken 参数。 有关更多详细信息,请访问超时故障排除指南
重试逻辑 有关要重试哪些错误以及 SDK 重试了哪些错误的详细信息,请参阅设计指南。 对于配置有多个区域的帐户,在某些场景中,SDK 会自动重试对其他区域的操作。 有关特定于 .NET 的实现详细信息,请访问 SDK 源存储库
缓存数据库/集合名称 从配置中检索数据库和容器的名称,或者一开始就将其缓存。 ReadDatabaseAsyncReadDocumentCollectionAsync 以及 CreateDatabaseQueryCreateDocumentCollectionQuery 之类的调用将导致对服务的元数据调用,这些调用使用系统预留的 RU 限制。 还应该只使用一次 CreateIfNotExist 来设置数据库。 总体而言,不应频繁执行这些操作。
批量支持 在可能不需要针对延迟进行优化的情况下,建议为转储大量数据启用批量支持
并行查询 Azure Cosmos DB SDK 支持并行运行查询,以便在查询时改善延迟和提高吞吐量。 建议将 QueryRequestsOptions 中的 MaxConcurrency 属性设置为你拥有的分区数。 如果不知道分区数,请先使用 int.MaxValue,这将为你提供最佳延迟。 然后减少此数值,直至符合环境的资源限制,以避免高 CPU 问题。 另外,可将 MaxBufferedItemCount 设置为预期返回的结果数,以限制预提取结果的数目。
性能测试回退 对应用程序执行测试时,应该按 RetryAfter 间隔实现回退。 允许回退有助于确保最大程度地减少重试之间的等待时间。
索引 Azure Cosmos DB 的索引策略还允许使用索引路径(IndexingPolicy.IncludedPaths 和 IndexingPolicy.ExcludedPaths)指定要在索引中包括或排除的文档路径。 确保从索引中排除未使用的路径以加快写入速度。 有关如何使用 SDK 创建索引的详细信息,请参阅性能提示 .NET SDK v3
文档大小 指定操作的请求费用与文档大小直接相关。 建议减小文档的大小,因为对大型文档执行操作比对小型文档执行操作成本更高。
增加线程/任务数目 由于对 Azure Cosmos DB 的调用是通过网络执行的,可能需要改变请求的并发度,以便最大程度地减少客户端应用程序等待请求的时间。 例如,如果使用 .NET 任务并行库,请创建数百个读取或写入 Azure Cosmos DB 的任务。
启用查询指标 对于后端查询执行的其他日志记录,可以使用 .NET SDK 启用 SQL 查询指标。 有关如何收集 SQL 查询指标的详细信息,请参阅查询指标和性能
SDK 日志记录 针对未完成场景(例如异常或请求超出预期延迟)记录 SDK 诊断
DefaultTraceListener DefaultTraceListener 在生产环境中造成性能问题,导致高 CPU 和 I/O 瓶颈。 确保使用的是最新的 SDK 版本或从应用程序中删除 DefaultTraceListener
避免在标识符中使用任何特殊字符 某些字符受到限制,不能在某些标识符中使用:“/”、“\”、“?”、“#”。 一般建议不要在像数据库名称、集合名称、项 ID 或分区键这样的标识符中使用任何特殊字符,以避免任何意外行为。

捕获诊断

SDK 中的所有响应(包括 CosmosException)都具有 Diagnostics 属性。 此属性记录与单一请求相关的所有信息,包括是否发生重试或任何瞬时故障。

诊断以字符串形式返回。 字符串会随每个版本而不断变化并进行改进,以对不同场景进行故障排除。 对于 SDK 的每个版本,字符串对格式设置都有中断性变更。 为避免发生中断性变更,请勿分析字符串。 以下代码示例展示了如何使用 .NET SDK 读取诊断日志:

try
{
    ItemResponse<Book> response = await this.Container.CreateItemAsync<Book>(item: testItem);
    if (response.Diagnostics.GetClientElapsedTime() > ConfigurableSlowRequestTimeSpan)
    {
        // Log the response.Diagnostics.ToString() and add any additional info necessary to correlate to other logs 
    }
}
catch (CosmosException cosmosException)
{
    // Log the full exception including the stack trace with: cosmosException.ToString()
    
    // The Diagnostics can be logged separately if required with: cosmosException.Diagnostics.ToString()
}

// When using Stream APIs
ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey, stream);
if (response.Diagnostics.GetClientElapsedTime() > ConfigurableSlowRequestTimeSpan || !response.IsSuccessStatusCode)
{
    // Log the diagnostics and add any additional info necessary to correlate to other logs with: response.Diagnostics.ToString()
}

适用于 HTTP 连接的最佳做法

无论配置的连接模式如何,.NET SDK 都会使用 HttpClient 来执行 HTTP 请求。 在直接模式下,HTTP 用于元数据操作,在网关模式下,HTTP 同时用于数据平面和元数据操作。 HttpClient 的基础知识之一是通过自定义共用连接生存期来确保 HttpClient 可以响应帐户上的 DNS 更改。 只要共用连接保持打开状态,它们就不会对 DNS 更改做出反应。 此设置会强制定期关闭共用连接,从而确保应用程序对 DNS 更改做出响应。 建议根据连接模式和工作负载来自定义此值,以平衡频繁创建新连接对性能的影响,并满足响应 DNS 更改(可用性)的需求。 5 分钟的值将是一个很好的开始,如果它对性能造成影响(尤其是在网关模式下),则可以递增。

可以通过 CosmosClientOptions.HttpClientFactory 注入自定义 HttpClient,例如:

// Use a Singleton instance of the SocketsHttpHandler, which you can share across any HttpClient in your application
SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler();
// Customize this value based on desired DNS refresh timer
socketsHttpHandler.PooledConnectionLifetime = TimeSpan.FromMinutes(5);

CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
{
    // Pass your customized SocketHttpHandler to be used by the CosmosClient
    // Make sure `disposeHandler` is `false`
    HttpClientFactory = () => new HttpClient(socketsHttpHandler, disposeHandler: false)
};

// Use a Singleton instance of the CosmosClient
return new CosmosClient("<connection-string>", cosmosClientOptions);

如果使用 .NET 依赖项注入,则可以简化单一实例过程:

SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler();
// Customize this value based on desired DNS refresh timer
socketsHttpHandler.PooledConnectionLifetime = TimeSpan.FromMinutes(5);
// Registering the Singleton SocketsHttpHandler lets you reuse it across any HttpClient in your application
services.AddSingleton<SocketsHttpHandler>(socketsHttpHandler);

// Use a Singleton instance of the CosmosClient
services.AddSingleton<CosmosClient>(serviceProvider =>
{
    SocketsHttpHandler socketsHttpHandler = serviceProvider.GetRequiredService<SocketsHttpHandler>();
    CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
    {
        HttpClientFactory = () => new HttpClient(socketsHttpHandler, disposeHandler: false)
    };

    return new CosmosClient("<connection-string>", cosmosClientOptions);
});

使用网关模式时的最佳做法

使用网关模式时,增加每台主机的 System.Net MaxConnections。 使用网关模式时,Azure Cosmos DB 请求是通过 HTTPS/REST 发出的。 这些请求受制于每个主机名或 IP 地址的默认连接限制。 可能需要将 MaxConnections 设置为较大的值(从 100 到 1,000),以便客户端库能够同时使用多个连接来访问 Azure Cosmos DB。 在 .NET SDK 1.8.0 及更高版本中,ServicePointManager.DefaultConnectionLimit 的默认值为 50。 若要更改该值,可以将 CosmosClientOptions.GatewayModeMaxConnectionLimit 设置为较高的值。

具有大量写入操作的工作负载的最佳做法

对于具有大量创建有效负载的工作负荷,请将 EnableContentResponseOnWrite 请求选项设置为 false。 该服务将不再将创建或更新的资源返回给 SDK。 通常,因为应用程序具有正在创建的对象,因此不需要服务即可将其返回。 标头值仍可访问,例如请求费用。 禁用内容响应有助于提高性能,因为 SDK 不再需要分配内存或序列化响应正文。 此外还会降低网络带宽的使用率,从而进一步提高性能。

重要

EnableContentResponseOnWrite 设置为 false 也会禁用触发器操作的响应。

适用于多租户应用的最佳做法

如果多租户在使用多个应用,且每个租户由同一 Azure Cosmos DB 帐户内的不同数据库、容器或分区键表示,则应使用单个客户端实例。 单一客户端实例可与一个帐户中的所有数据库、容器和分区键进行交互,是采用单一实例模式的最佳做法。

不过,当每个租户由不同的 Azure Cosmos DB 帐户表示时,需要为每个帐户创建单独的客户端实例。 虽然单一实例模式仍适用于每个客户端(在应用生命周期内,每个帐户的每个客户端),但如果租户数量较多,则客户端数量可能很难管理。 连接数量可能会超出计算环境的上限,导致连接问题

此类情况下,建议采取以下做法:

  • 了解计算环境的上限(CPU 和连接资源)。 建立在条件允许时,使用拥有至少 4 核与 8GB 内存的虚拟机。
  • 根据计算环境的上限来确定单个计算实例可以处理的客户端实例数量,继而确定有多少租户。 可以根据所选择的连接模式估算出每个客户端会打开的连接数量
  • 评估租户在各实例之间的分布情况。 如果每个实例可以成功处理一定数量的租户,则随着租户数量越来越多,可通过负载均衡以及将租户路由到其他计算实例来进行缩放。
  • 如果工作负载零星地分散在各处,请考虑使用“最不常用的”缓存作为结构来保存客户端实例,并清理某个时间范围内没有被访问的租户的客户端。 在 .NET 中,选项之一是 MemoryCacheEntryOptions。在此选项中,可以使用 RegisterPostEvictionCallback清理非活动客户端,并使用 SetSlidingExpiration 来定义保存非活动连接的最长时间。
  • 使用网关模式进行评估以减少网络连接数。
  • 如果使用的是直连模式,请考虑在直连模式配置上调整 CosmosClientOptions.IdleTcpConnectionTimeoutCosmosClientOptions.PortReuseMode,并让连接数始终保持在可控范围内。

后续步骤

如果需要使用一个示例应用程序来评估 Azure Cosmos DB,以便在数个客户端计算机上实现高性能方案,请参阅使用 Azure Cosmos DB 进行性能和缩放测试

若要深入了解如何设计应用程序以实现缩放和高性能,请参阅 Azure Cosmos DB 中的分区和缩放

尝试为迁移到 Azure Cosmos DB 进行容量计划? 可以使用有关现有数据库群集的信息进行容量规划。