共用方式為

Azure Cosmos DB .NET SDK 的最佳做法

适用范围: NoSQL

本文演示使用 Azure Cosmos DB .NET SDK 的最佳做法。 通过遵循这些做法,可帮助你提高延迟、可用性并提高整体性能。

清单

复选框 使用者 DESCRIPTION
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 源存储库
缓存数据库/集合名称 从配置中检索数据库和容器的名称,或者一开始就将其缓存。 调用,如 ReadDatabaseAsyncReadDocumentCollectionAsyncCreateDatabaseQueryCreateDocumentCollectionQuery,会导致对服务进行元数据调用,这些调用会消耗系统保留的 RU 限制。 还应该只使用一次 CreateIfNotExist 来设置数据库。 总体而言,不应频繁执行这些操作。
批量支持 在可能不需要针对延迟进行优化的情况下,建议启用 批量支持 来转储大量数据。
并行查询 Azure Cosmos DB SDK 支持并行运行查询,以便在查询时改善延迟和提高吞吐量。 建议将 MaxConcurrency 中的 QueryRequestsOptions 属性设置为你拥有的分区数。 如果不知道分区数,请首先使用 int.MaxValue,从而提供最佳延迟。 然后减少此数值,直至符合环境的资源限制,以避免高 CPU 问题。 另外,可将 MaxBufferedItemCount 设置为预期返回的结果数,以限制预提取结果的数目。
性能测试退避策略 对应用程序执行测试时,应该按 RetryAfter 间隔实现回退。 允许回退有助于确保最大程度地减少重试之间的等待时间。
索引 Azure Cosmos DB 索引策略还允许您使用索引路径(IndexingPolicy.IncludedPathsIndexingPolicy.ExcludedPaths)来指定要包含或排除在索引中的文档路径。 确保从索引中排除未使用的路径以加快写入速度。 有关如何使用 SDK 创建索引的详细信息,请参阅 索引策略
文档大小 指定操作的请求费用与文档大小直接相关。 建议减小文档的大小,因为对大型文档执行操作比对小型文档执行操作成本更高。
增加线程/任务数目 由于对 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 进行容量规划? 可以使用有关现有数据库群集的信息进行容量规划。