适用范围: NoSQL
Azure Cosmos DB 是一个快速、弹性的分布式数据库,可以在提供有保证的延迟与吞吐量级别的情况下无缝缩放。 凭借 Azure Cosmos DB,无需对体系结构进行重大更改或编写复杂的代码即可缩放数据库。 扩展和缩减操作就像执行单个 API 调用一样简单。 若要了解详细信息,请参阅预配容器吞吐量或预配数据库吞吐量。
因为 Azure Cosmos DB 是通过网络调用访问的,因此,使用 SQL .NET SDK 时可以通过进行客户端优化来获得最高性能。
如果你尝试改善数据库性能,请考虑以下部分中提供的选项。
启用服务器端垃圾回收
在某些情况下,降低垃圾回收的频率可能会有帮助。 在 .NET 中,将 gcServer 设置为 true
。
横向扩展客户端工作负载
如果你在高吞吐量级别或速率大于 50,000 请求单位/秒(RU/秒)的情况下进行测试,则客户端应用程序可能会成为工作负荷瓶颈。 这是因为计算机可能会达到 CPU 或网络利用率的上限。 如果达到此阶段,可以跨多个服务器横向扩展客户端应用程序,以进一步推动 Azure Cosmos DB 帐户的发展。
注意
CPU 使用率高可能会导致延迟增加和请求超时异常。
请勿通过在热路径中以及/或者在执行某个项操作之前调用 Create...IfNotExistsAsync
和/或 Read...Async
来验证数据库和/或容器是否存在。 如果希望删除这些数据库和/或容器,那么,只应在必要时在应用程序启动时进行验证(否则不需要进行验证)。 这些元数据操作将会生成额外的端到端延迟、没有 SLA,以及具有它们自己单独的限制(不像数据操作那样缩放)。
某些环境启用了 .NET DefaultTraceListener。 DefaultTraceListener 在生产环境中造成性能问题,导致高 CPU 和 I/O 瓶颈。 检查并确保已对应用程序禁用 DefaultTraceListener,方法是将其从生产环境中的 TraceListeners 删除。
最新 SDK 版本(高于 3.23.0)在检测到它时会自动将其删除;使用旧版本时,可通过以下方式将其删除:
if (!Debugger.IsAttached)
{
Type defaultTrace = Type.GetType("Microsoft.Azure.Cosmos.Core.Trace.DefaultTrace,Microsoft.Azure.Cosmos.Direct");
TraceSource traceSource = (TraceSource)defaultTrace.GetProperty("TraceSource").GetValue(null);
traceSource.Listeners.Remove("Default");
// Add your own trace listeners
}
有关在 Azure Cosmos DB 中配置高可用性的一般指南,请参阅 Azure Cosmos DB 中的高可用性。
除了在数据库平台中设置良好的基础性设置外,还可以在 .NET SDK 中实施基于阈值的可用性策略,从而在中断方案中有所帮助。 此功能提供高级机制来解决特定的延迟和可用性挑战,超越默认情况下内置于 SDK 中的跨区域重试功能。 这可以显著提高应用程序的复原能力和性能,尤其是在高负载或降级条件下。
基于阈值的可用性策略可以通过向次要区域(如定义) ApplicationPreferredRegions
发送并行读取请求并接受最快的响应来提高尾部延迟和可用性。 此方法可以大幅降低区域性中断或高延迟条件对应用程序性能的影响。
示例配置:
可以使用 CosmosClientBuilder
完成相应的配置:
CosmosClient client = new CosmosClientBuilder("connection string")
.WithApplicationPreferredRegions(
new List<string> { "East US", "East US 2", "West US" } )
.WithAvailabilityStrategy(
AvailabilityStrategy.CrossRegionHedgingStrategy(
threshold: TimeSpan.FromMilliseconds(500),
thresholdStep: TimeSpan.FromMilliseconds(100)
))
.Build();
也可以配置选项并将其添加到 CosmosClient
:
CosmosClientOptions options = new CosmosClientOptions()
{
AvailabilityStrategy
= AvailabilityStrategy.CrossRegionHedgingStrategy(
threshold: TimeSpan.FromMilliseconds(500),
thresholdStep: TimeSpan.FromMilliseconds(100)
)
ApplicationPreferredRegions = new List<string>() { "East US", "East US 2", "West US"},
};
CosmosClient client = new CosmosClient(
accountEndpoint: "account endpoint",
authKeyOrResourceToken: "auth key or resource token",
clientOptions: options);
工作原理:
初始请求:在 T1 时,向主要区域(例如美国东部)发出读取请求。 SDK 等待响应的时间最多为 500 毫秒(
threshold
值)。第二次请求:如果在 500 毫秒内主要区域没有响应,则会向下一个首选区域(例如美国东部 2)发送并行请求。
第三次请求:如果在 600 毫秒(500 毫秒 + 100 毫秒,
thresholdStep
值)内主要区域和次要区域均没有响应,则 SDK 会向第三个首选区域(例如美国西部)发送另一个并行请求。最快响应获胜:哪个区域最先响应,就接受哪个区域的响应,并忽略其他并行请求。
注意
如果第一个首选区域返回非暂时性错误状态代码(例如找不到文档、授权错误、冲突等),则操作本身将快速失败,因为可用性策略在这种情况下不会有任何好处。
连接策略:使用直接连接模式
.NET V3 SDK 默认连接模式是使用 TCP 协议直接连接。 在 CosmosClient
中创建 CosmosClientOptions
实例时,可以配置连接模式。 若要详细了解不同的连接性选项,请参阅连接性模式一文。
CosmosClient client = new CosmosClient(
"<nosql-account-endpoint>",
tokenCredential
new CosmosClientOptions
{
ConnectionMode = ConnectionMode.Gateway // ConnectionMode.Direct is the default
}
);
临时端口耗尽
如果实例上的连接量较高或端口使用率较高,请先确认客户端实例是否为单一实例。 换句话说,客户端实例在应用程序生存期内应是唯一的。
在 TCP 协议上运行时,客户端使用长生存期连接来优化延迟。 这与 HTTPS 协议相反,后者在处于非活动状态两分钟后终止连接。
在具有稀疏访问且与网关模式访问相比连接计数更高的情况下,你可以:
- 将 CosmosClientOptions.PortReuseMode 属性配置为
PrivatePortPool
(在 Framework 版本 4.6.1 及以上和 .NET Core 版本 2.0 及以上时有效)。 此属性使 SDK 可以针对各种 Azure Cosmos DB 目标终结点使用一小部分临时端口。 - 将 CosmosClientOptions.IdleTcpConnectionTimeout 属性配置为大于或等于 10 分钟。 建议值为 20 分钟到 24 小时。
出于性能考虑,请将客户端并置在同一 Azure 区域中
如果可能,请将任何调用 Azure Cosmos DB 的应用程序放在 Azure Cosmos DB 数据库所在的区域。 根据请求采用的路由,各项请求从客户端传递到 Azure 数据中心边界时的此类延迟可能有所不同。
确保调用应用程序位于预配的 Azure Cosmos DB 终结点所在的 Azure 区域即可尽可能降低延迟。 有关可用区域的列表,请参阅 Azure 区域。
增加线程/任务数目
由于对 Azure Cosmos DB 的调用是通过网络执行的,可能需要改变请求的并发度,以便最大程度地减少客户端应用程序等待请求的时间。 例如,如果使用 .NET 任务并行库,请创建大约数百个在 Azure Cosmos DB 中进行读取或写入操作的任务。
启用加速网络以减少延迟和 CPU 抖动
建议按照说明在 Windows(单击以获取说明)或 Linux(单击以获取说明) Azure VM 中启用加速网络,以最大程度提高性能。
没有加速网络,在 Azure VM 与其他 Azure 资源之间传输的 IO 可能会不必要地通过主机和虚拟交换机(位于 VM 与其网卡之间)进行路由。 在数据路径中以内联方式放置主机和虚拟交换机不仅会增加信道中的延迟和抖动,还会占用 VM 的 CPU 周期。 使用加速网络时,VM 直接与 NIC 连接,没有中介;以前由主机和虚拟交换机处理的任何网络策略细节现在都在 NIC 的硬件中处理;主机和虚拟交换机将被绕过。 通常情况下,当启用加速网络后,应会降低延迟并提高吞吐量,同时会提高延迟一致性并降低 CPU 利用率。
限制:虚拟机操作系统(OS)必须支持加速网络,且只能在虚拟机已停止并解除分配的状态下启用。 不能通过 Azure 资源管理器部署此 VM。 应用服务未启用加速网络。
有关更多详细信息,请参阅 Windows 和 Linux 说明。
安装最新的 SDK
Azure Cosmos DB SDK 正在不断改进以提供最佳性能。 若要确定最新的 SDK 并查看改进内容,请参阅 Azure Cosmos DB SDK。
使用流 API
.NET SDK V3 包含的流 API 可以在不序列化的情况下接收和返回数据。
如果中间层应用程序不直接使用 SDK 的响应,而是将其中继到其他应用程序层,则此类应用程序可以受益于流 API。 有关流处理的示例,请参阅项管理示例。
在应用程序生存期内使用单一实例 Azure Cosmos DB 客户端
每个 CosmosClient
实例都是线程安全的,在直接模式下运行时可以进行高效的连接管理和地址缓存。 若要实现有效的连接管理并提高 SDK 客户端性能,建议在应用程序的生存期内,对应用互动的每个帐户使用单个实例AppDomain
。
对于处理多个帐户的多租户应用程序,请参阅相关的最佳做法。
使用 Azure Functions 时,实例还应遵循现有指南并维护单个实例。
避免阻塞调用
Azure Cosmos DB SDK 应设计为同时处理多个请求。 异步 API 允许较小线程池处理数千个并发请求,无需等待阻塞调用。 线程可以处理另一个请求,而不是等待长时间运行的同步任务完成。
使用 Azure Cosmos DB SDK 的应用中的一个常见性能问题是阻止可能为异步的调用。 许多同步阻塞调用都会导致线程池饥饿和响应时间降低。
禁止行为:
- 通过调用 Task.Wait 或 Task.Result 阻止异步执行。
- 使用 Task.Run 使同步 API 异步。
- 获取常见代码路径中的锁。 Azure Cosmos DB .NET SDK 在构建为并行运行代码时性能最高。
- 调用 Task.Run 并立即等待它完成。 ASP.NET Core 已经在普通线程池线程上运行应用代码,因此调用 Task.Run 只会导致不必要的额外线程池计划。 即使计划的代码会阻止某个线程,Task.Run 也不会阻止该线程。
- 不要在
Container.GetItemLinqQueryable<T>()
上使用 ToList(),它使用阻塞调用来同步排出查询。 使用 ToFeedIterator() 异步清空查询。
建议做法:
以异步方式调用 Azure Cosmos DB .NET API。
为了获益于 async/await 模式,整个调用堆栈都是异步的。
探查器(例如 PerfView)可用于查找频繁添加到线程池中的线程。
Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start
事件表示有线程被添加到线程池。
禁用写入操作时的内容响应
对于需要处理大量创建负载的工作负荷,请将 EnableContentResponseOnWrite
请求选项设置为 false
。 该服务将不再将创建或更新的资源返回给 SDK。 通常,因为应用程序具有正在创建的对象,因此不需要服务即可将其返回。 标头值仍可访问,例如请求费用。 禁用内容响应有助于提高性能,因为 SDK 不再需要分配内存或序列化响应正文。 此外还会降低网络带宽的使用率,从而进一步提高性能。
ItemRequestOptions requestOptions = new ItemRequestOptions() { EnableContentResponseOnWrite = false };
ItemResponse<Book> itemResponse = await this.container.CreateItemAsync<Book>(book, new PartitionKey(book.pk), requestOptions);
// Resource will be null
itemResponse.Resource
启用 Bulk 以优化吞吐量,而不是优化延迟
对于工作负荷需要大吞吐量且延迟不是那么重要的场景,请启用“Bulk”。 若要详细了解如何启用 Bulk 功能,并了解应将其用于哪些场景,请参阅 Bulk 支持简介。
在使用网关模式时增大每台主机的 System.Net MaxConnections
使用网关模式时,Azure Cosmos DB 请求是通过 HTTPS/REST 发出的。 这些请求受制于每个主机名或 IP 地址的默认连接限制。 可能需要将 MaxConnections
设置为较大的值(从 100 到 1,000),以便客户端库能够同时使用多个连接来访问 Azure Cosmos DB。 在 .NET SDK 1.8.0 及更高版本中,ServicePointManager.DefaultConnectionLimit 的默认值为 50。 若要更改该值,可以将 Documents.Client.ConnectionPolicy.MaxConnectionLimit
设置为较高的值。
增加线程/任务数目
请参阅本文“网络”部分中的增加线程/任务数目。
有关查询操作,请参阅查询的性能提示。
从索引中排除未使用的路径以加快写入速度
Azure Cosmos DB 的索引策略还允许使用索引路径(IndexingPolicy.IncludedPaths 和 IndexingPolicy.ExcludedPaths)指定要在索引中包括或排除的文档路径。
在事先已知查询模式的情况下,仅对所需路径编制索引可以提高写入性能,减少写入操作时的 RU 费用和索引存储。 这是因为,索引成本与已编制索引的唯一路径数目直接相关。 例如,以下代码演示了如何使用“*”通配符从索引中排除整个文档部分(子树):
var containerProperties = new ContainerProperties(id: "excludedPathCollection", partitionKeyPath: "/pk" );
containerProperties.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" });
containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/nonIndexedContent/*");
Container container = await this.cosmosDatabase.CreateContainerAsync(containerProperties);
有关索引的详细信息,请参阅 Azure Cosmos DB 索引策略。
度量和优化 RU/秒使用量较低的情况
Azure Cosmos DB 提供一组丰富的数据库操作。 这些操作包括用户定义函数 (UDF) 的关系和分层查询、存储过程和触发器,全都在数据库集合中的文档上进行。
与其中每个操作关联的成本取决于完成该操作所需的 CPU、IO 和内存。 可以考虑将请求单位作为所需资源的单个度量,以便执行各种数据库操作和服务应用程序请求,而不必考虑和管理硬件资源。
吞吐量的预配取决于为每个容器设置的请求单位数。 请求单位消耗以每秒单位数速率进行评估。 如果应用程序的速率超过了为其容器预配的请求单位速率,则会受到限制,直到该速率降到容器的预配级别以下。 如果应用程序需要较高级别的吞吐量,可以通过预配更多请求单位来增加吞吐量。
查询的复杂性会影响操作消耗的请求单位数量。 谓词数、谓词性质、UDF 文件数目和源数据集的大小都会影响查询操作的成本。
若要度量任一操作(创建、更新或删除)的开销,请检查 x-ms-request-charge 标头(或者 .NET SDK 的 RequestCharge
或 ResourceResponse<T>
中的等效 FeedResponse<T>
属性),以度量这些操作消耗的请求单位数:
// Measure the performance (Request Units) of writes
ItemResponse<Book> response = await container.CreateItemAsync<Book>(myBook, new PartitionKey(myBook.PkValue));
Console.WriteLine("Insert of item consumed {0} request units", response.RequestCharge);
// Measure the performance (Request Units) of queries
FeedIterator<Book> queryable = container.GetItemQueryIterator<ToDoActivity>(queryString);
while (queryable.HasMoreResults)
{
FeedResponse<Book> queryResponse = await queryable.ExecuteNextAsync<Book>();
Console.WriteLine("Query batch consumed {0} request units", queryResponse.RequestCharge);
}
在此标头中返回的请求费用是已预配吞吐量(即 2,000 RU/秒)的一小部分。 例如,如果上述查询返回 1,000 个 1-KB 的文档,则操作成本是 1,000。 因此,在一秒内,服务器在对后续请求进行速率限制之前只接受两个此类请求。 有关详细信息,请参阅请求单位和请求单位计算器。
处理速率限制/请求速率太大
客户端尝试超过为帐户保留的吞吐量时,服务器的性能不会降低,并且不会使用超过保留级别的吞吐量容量。 服务器会提前结束请求并返回 RequestRateTooLarge(HTTP 状态代码为 429)错误。 它会返回 x-ms-retry-after-ms 标头,指示重试该请求之前用户必须等待的时间(以毫秒为单位)。
HTTP Status 429,
Status Line: RequestRateTooLarge
x-ms-retry-after-ms :100
SDK 全部都会隐式捕获此响应,并遵循服务器指定的 retry-after 标头,并重试请求。 除非多个客户端同时访问帐户,否则下次重试就会成功。
如果累计有多个客户端持续在超过请求速率的情况下运行,则当前由客户端在内部设置为 9 的默认重试计数可能并不够用。 在这种情况下,客户端会向应用程序引发 CosmosException,状态代码为 429。
可以通过在 RetryOptions
实例上设置 CosmosClientOptions
来更改默认重试计数。 默认情况下,如果请求继续以高于请求速率的方式运行,则会在 30 秒的累积等待时间后返回 CosmosException,状态代码为 429。 即使当前的重试计数小于最大重试计数(无论当前值是默认值 9 还是用户定义的值),也会返回此错误。
自动重试行为有助于改善大多数应用程序的复原能力和可用性。 但是,在进行性能基准测试时(尤其是在测量延迟时),这可能不是最佳行为。 如果实验触发服务器节流并导致客户端 SDK 自动重试,则客户端观测到的延迟会剧增。 若要避免性能实验期间出现延迟高峰,可以测量每个操作返回的负载,确保请求以低于预留的请求速率的方式运行。
有关详细信息,请参阅请求单位。
针对较小文档进行设计以提高吞吐量
指定操作的请求费用(即请求处理成本)与文档大小直接相关。 大型文档的操作成本高于小型文档的操作成本。
如果需要使用一个示例应用程序来评估 Azure Cosmos DB,以便在数个客户端计算机上实现高性能方案,请参阅使用 Azure Cosmos DB 进行性能和缩放测试。
若要深入了解如何设计应用程序以实现缩放和高性能,请参阅 Azure Cosmos DB 中的分区和缩放。