Compartilhar via

Azure Cosmos DB .NET SDK 的最佳做法

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

清单

复选框 Subject Description
SDK 版本 始终使用 最新版本 的 Azure Cosmos DB SDK,以获得最佳性能。
单一实例客户端 在应用程序的生命周期内,使用一个 CosmosClient以获得 更好的性能
区域 请确保尽可能在与 Azure Cosmos DB 帐户相同的 Azure 区域中运行应用程序以减少延迟。 启用 2-4 个区域并在多个区域中复制帐户以获得最佳可用性。 对于生产工作负载,请启用服务托管故障转移。 如果没有此配置,帐户在写入区域中断期间会丧失写入可用性,因为缺乏区域连接导致手动故障转移无法成功。 若要了解如何使用 .NET SDK 添加多个区域,请参阅本教程
可用性和故障转移 在 v3 SDK 中设置 ApplicationPreferredRegionsApplicationRegion,并在 v2 SDK 中使用 PreferredLocationspreferred regions list。 在故障转移期间,系统会将写入操作发送到当前写入区域,并将所有读取操作发送到首选区域列表中的第一个区域。 有关区域故障转移机制的详细信息,请参阅可用性问题排查指南
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 序列化操作。 不自动管理此依赖项 - 必须在project中显式添加 Newtonsoft.Json 作为直接依赖项。

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

重要

即使通过 CosmosClientOptions.UseSystemTextJsonSerializerWithOptions 对用户定义的类型使用 System.Text.Json,也需要此依赖项,因为 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 在内部使用它用于系统类型
  • 显式固定版本 - 不要依赖于可传递依赖项解析
  • Monitor 警告 - 将 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可用于定义保持非活动连接的最大时间。
  • 使用网关模式进行评估以减少网络连接数。
  • 使用Direct mode时,请考虑在direct 模式配置中调整CosmosClientOptions.IdleTcpConnectionTimeoutCosmosClientOptions.PortReuseMode,以关闭未使用的连接,并控制连接的数量。

后续步骤

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