你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

Azure Cosmos DB .NET SDK 的最佳做法

适用于: NoSQL

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

观看以下视频,详细了解如何通过 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 或分区键这样的标识符中使用任何特殊字符,以避免任何意外行为。

管理 Newtonsoft.Json 依赖项

概述

Azure Cosmos DB .NET SDK 依赖于 Newtonsoft.Json 进行 JSON 序列化操作。 此依赖项不会自动管理 - 必须在项目中显式添加 Newtonsoft.Json 为直接依赖项。

SDK 在内部针对 Newtonsoft.Json 10.x 进行编译,该 SDK 具有已知的安全漏洞。 虽然 SDK 在技术上与 10.x 兼容,但 SDK 对 Newtonsoft.Json 的使用并不容易受到报告安全问题的影响,但仍 建议使用版本 13.0.3 或更高版本 来避免潜在的安全问题或冲突。 13.x 版本包括中断性变更,但 SDK 的使用模式与这些更改兼容。

重要说明

即使通过 System.Text.Json 使用 CosmosClientOptions.UseSystemTextJsonSerializerWithOptions 用户自定义类型,也需要此依赖项,因为 SDK 的内部操作仍对系统类型使用 Newtonsoft.Json。

使用 Azure Cosmos DB .NET SDK v3 时,始终将版本 13.0.3 或更高版本显式添加Newtonsoft.Json为直接依赖项。 由于已知的安全漏洞,请勿使用版本 10.x。

对于标准 .csproj 项目

<ItemGroup>
  <PackageReference Include="Microsoft.Azure.Cosmos" Version="3.47.0" />
  <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>

对于使用中央包管理的项目

如果项目使用 Directory.Packages.props

<Project>
  <ItemGroup>
    <PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.47.0" />
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
  </ItemGroup>
</Project>

版本冲突疑难解答

缺少 Newtonsoft.Json 参考

如果遇到构建错误,例如:

The Newtonsoft.Json package must be explicitly referenced with version >= 10.0.2. Please add a reference to Newtonsoft.Json or set the 'AzureCosmosDisableNewtonsoftJsonCheck' property to 'true' to bypass this check.

此错误是由 Cosmos DB SDK 的生成目标有意生成的,以确保正确配置依赖项。

应用程序解决方案:

添加对 Newtonsoft.Json 的显式引用,如上面的 “建议配置” 部分所示。

图书馆解决方案:

如果要生成库(而不是应用程序),并且想要将 Newtonsoft.Json 依赖项延迟到库的使用者,可以通过在以下项 .csproj中设置 MSBuild 属性来绕过此检查:

<PropertyGroup>
  <AzureCosmosDisableNewtonsoftJsonCheck>true</AzureCosmosDisableNewtonsoftJsonCheck>
</PropertyGroup>

警告

仅当生成最终用户将提供 Newtonsoft.Json 依赖项的库时,才使用此绕过。 对于应用程序,请始终添加显式引用。

包版本冲突

如果遇到构建错误,例如:

error NU1109: Detected package downgrade: Newtonsoft.Json from 13.0.4 to centrally defined 13.0.3

Solution:

  1. 通过检查哪些包需要较新版本来确定所需的版本

    dotnet list package --include-transitive | Select-String "Newtonsoft.Json"
    
  2. 更新集中式包版本 以匹配或超过所需的最高版本:

    <PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
    
  3. 清理并重新生成

    dotnet clean
    dotnet restore
    dotnet build
    

版本兼容性

下表显示了每个 Cosmos DB SDK 版本的 Newtonsoft.Json 的最低建议安全版本 。 虽然 SDK 在技术上可与 10.x 配合使用,但由于安全漏洞,不应使用这些版本。

Cosmos DB SDK 版本 最低安全版本 Recommended
3.47.0+ 13.0.3 13.0.4
3.54.0+ 13.0.4 13.0.4

小窍门

使用 .NET Aspire 13.0.0 或更高版本时,请确保 Newtonsoft.Json 版本为 13.0.4,以避免与 Aspire 的 Azure 组件冲突。

最佳做法

  • 始终添加为直接依赖项 - SDK 不会自动为你管理此依赖项
  • 使用版本 13.0.3 或更高版本 - 由于已知的安全漏洞,请勿使用 10.x,尽管技术兼容性
  • 即使 System.Text.Json 是必需的 - 即使使用 UseSystemTextJsonSerializerWithOptionsNewtonsoft.Json,也必须包括 Newtonsoft.Json,因为 SDK 在内部使用它用于系统类型
  • 显式固定版本 - 不要依赖于可传递依赖项解析
  • 监视警告 - 将 NuGet 包降级警告 (NU1109) 视为 CI/CD 管道中的错误

捕获诊断

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 进行容量计划? 可以根据有关现有数据库群集的信息进行容量计划。