ThreadPool.SetMinThreads(100,100); //(Determine the right number for your application)
有关详细信息,请参阅 ThreadPool.SetMinThreads 方法。
不受限制的并行度
虽然并行度有助于提高性能,但在使用不受限制的并行度时应保持谨慎,因为这意味着对线程数或并行请求数没有限制。 请务必限制上传或下载数据、访问同一存储帐户中的多个分区以及访问同一分区中的多个项的并行请求。 如果并行度不受限制,应用程序则可能会超出客户端设备的承受程度或超出存储帐户的可伸缩性目标,导致延迟和限制时间增长。
为获得最佳性能,请始终使用 Microsoft 提供的最新客户端库和工具。 Microsoft Azure 存储客户端库适用于各种语言。 Azure 存储还支持 PowerShell 和 Azure CLI。 Microsoft 正在积极开发这些客户端库和工具,并注重其性能,使用最新服务版本对其进行更新,确保这些工具可以在内部协调好许多经过证实的做法。
处理服务错误
当服务无法处理请求时,Azure 存储会返回错误。 了解 Azure 存储在特定情况下可能返回的错误将有助于优化性能。
超时和服务器繁忙错误
如果应用程序即将达到可伸缩性限制,Microsoft Azure 存储可能会对其进行限制。 在某些情况下,Azure 存储可能会出于某种暂时性的状况而无法处理请求。 对于这两种情况,服务可能返回 503(服务器繁忙)或 500(超时)错误。 如果服务正在对数据分区进行重新均衡以提高吞吐量,则也可能会发生这些错误。 通常,客户端应用程序应重试导致上述某种错误的操作。 但是,如果 Microsoft Azure 存储因为应用程序即将超出可伸缩性目标而限制应用程序,或者其他某种原因导致服务无法为请求提供服务,则过于频繁的重试可能会使问题变得更糟。 建议使用指数退避重试策略,客户端库默认采用此行为。 例如,应用程序可能会在 2 秒后、4 秒后、10 秒后,以及 30 秒后进行重试,最后彻底放弃重试。 这样,应用程序可明显减少其在服务中施加的负载,而不会使得导致出现限制的行为恶化。
连接错误可以立即重试,因为它不是限制造成的,而且应该是暂时性的。
不可重试的错误
客户端库将处理重试,同时能够识别哪些错误可重试,哪些不可重试。 但是,如果直接调用 Azure 存储 REST API,则不应重试某些错误。 例如,400(错误的请求)错误表示客户端应用程序发送了一个无法处理的请求(因为该请求未采用预期的格式)。 每次重新发送此请求都会导致相同的响应,因此没有必要重试。 如果直接调用 Azure 存储 REST API,请注意潜在错误以及是否应重试这些错误。
有关 Azure 存储错误代码的详细信息,请参阅状态和错误代码。
本部分列出了多个快速配置设置,可以使用这些设置显著提高表服务的性能:
使用 JSON
从存储服务 2013-08-15 版开始,表服务就支持使用 JSON 而非基于 XML 的 AtomPub 格式来传输表数据。 使用 JSON 最多可以减少 75% 的有效负载大小,并可以显著提高应用程序的性能。
有关详细信息,请参阅文章 Microsoft Azure Tables: Introducing JSON(Microsoft Azure 表:JSON 简介)和 Payload Format for Table Service Operations(表服务操作的有效负载格式)。
禁用 Nagle
Nagle 的算法已跨 TCP/IP 网络进行了广泛的实施,是一种改进网络性能的方法。 不过,该方法并非适用于所有情况(例如高度交互式的环境)。 Nagle 的算法会对 Azure 表服务请求的性能造成负面影响,因此应尽量将其禁用。
数据的呈现和查询方式是影响表服务性能的单个最大因素。 虽然每个应用程序都不同,但本部分仍概要列出了一些通用的经过验证的做法,这些做法适用于:
高效的查询
高效的数据更新
表划分为分区。 存储在分区中的每个实体共享相同的分区键,并具有唯一的行键,用于在该分区中标识自己。 分区具有好处,但也带来了可伸缩性限制。
好处:可以在同一个分区中更新单个事务、原子事务和批处理事务的实体,每种事务最多包含 100 个单独的存储操作(总大小限制为 4 MB)。 此外,假定需要检索相同数量的实体,则在单个分区中查询数据要比跨多个分区查询数据更高效(不过,如果需要查询表数据,则请继续阅读以获取更进一步的建议)。
可伸缩性限制:对存储在单个分区中的实体的访问不能进行负载均衡,因为分区支持原子批处理事务。 因此,总体说来单个表分区的可伸缩性目标低于表服务的相应目标。
考虑到表和分区的这些特点,应该采用以下设计原则:
将客户端应用程序频繁更新或查询的数据放到同一分区中的同一工作逻辑单元。 例如,如果应用程序要聚合写入或者你要执行原子批处理操作,请将数据放到同一分区。 此外,与跨分区的数据相比,可以更高效地对单个分区中的数据进行查询。
将客户端应用程序不会插入、更新或查询的数据放到不同分区中的同一工作逻辑单元(即,单个查询或批量更新)。 请记住,单个表中的分区键没有数量限制,因此即使设置数百万个分区键也不是问题,也不会影响性能。 例如,如果应用程序是一个需要用户登录的热门网站,不妨使用用户 ID 作为分区键。
热分区是指这样一种分区,即收到了某个帐户的过多流量,但又无法对其进行负载均衡,因为该分区为单个分区。 一般情况下,热分区的创建有以下两种模式:
“仅追加”和“仅预置”模式
“仅追加”模式是指流向某个给定分区键的所有(或几乎所有)流量都会按当前时间增加或减少。 例如,假设应用程序使用当前日期作为日志数据的分区键。 此设计会导致所有插入内容进入表中的最后一个分区,并且系统无法正确地进行负载均衡。 如果进入该分区的流量超出分区级的可伸缩性目标,则会导致限制。 最好是确保将流量发送到多个分区,以便对跨表请求进行负载均衡。
高流量数据
如果分区方案导致单个分区的数据较其他分区的数据使用更为频繁,则也可能会看到限制现象,因为该分区达到了单个分区的可伸缩性目标。 最好是确保分区方案不会导致单个分区接近可伸缩性目标。
本部分介绍有关查询表服务的经过证实的做法。
有多种方式可指定需要查询的实体的范围。 以下列表描述了查询范围的每个选项。
点查询:点查询会同时指定要检索的实体的分区键和行键,因此可确切地检索到一个实体。 此类查询非常高效,应尽可能使用。
分区查询: 分区查询用于检索共享分区键的一组数据。 通常情况下,该查询会指定一系列行键值或者一系列用于某些实体属性和分区键的值。 这些查询效率不如点查询,应谨慎使用。
表查询:表查询用于检索没有共享通用分区键的一组实体。 此类查询效率不高,应尽可能避免使用。
通常情况下,应避免进行扫描(大于单个实体的查询),但如果必须要进行扫描,则应尝试对数据进行组织,使扫描仅检索所需数据,避免扫描或返回大量不需要的实体。
影响查询效率的另一关键因素是返回的实体数与查找返回的集合时扫描过的实体数的比率。 如果应用程序在执行表查询时使用了某个属性值的筛选器,而该属性值仅供 1% 的数据共享,则该查询需要扫描 100 个实体才会返回 1 个实体。 前面讨论的表可伸缩性目标均与所扫描的实体数相关,与返回的实体数无关:查询密度低很容易导致表服务限制应用程序,因为表服务在检索要查找的实体时需要扫描的实体过多。 有关如何避免限制的详细信息,请参阅标题为反规范化的部分。
限制返回的数据量
如果知道某个查询将要返回的实体并不是客户端应用程序所需要的,则应考虑使用筛选器来减少返回的集合的大小。 虽然没有返回到客户端的实体仍会计入可伸缩性限制,但应用程序的性能会提高,因为网络负载大小会下降,同时客户端应用程序必须处理的实体数会下降。 请记住,可伸缩性目标与扫描的实体数相关,因此查询在筛选掉许多实体后仍可能导致限制,即使返回很少的实体。 有关提高查询效率的详细信息,请参阅标题为查询密度的部分。
如果客户端应用程序只需表中实体提供的一组有限的属性,则可以使用投影来限制所返回数据集的大小。 就像使用筛选一样,投影有助于减少网络负载和客户端处理。
与使用关系数据库不同,根据经过验证的做法,若要提高表数据的查询效率,需对数据进行非规范化。 也就是说,需要将相同的数据复制到多个实体中(一个实体对应一个用于查找数据的键)以尽量降低查询在查找客户端所需数据时必须扫描的实体数,这样就不必扫描大量实体来查找应用程序需要的数据。 例如,在电子商务网站中,可能希望通过两种方式查找订单:按客户 ID(供此客户的订单)和按日期(提供某个日期的订单)。 在表存储中,最好是将实体(或者对实体的引用)存储两次 – 一次使用表名称、PK 和 RK 进行存储,以按客户 ID 快速查找,另一次则通过日期来加快查找速度。
插入、更新和删除
本部分介绍的经过验证的做法用于修改存储在表服务中的实体。
批处理事务在 Azure 存储中称为实体组事务。 实体组事务中的所有操作都必须位于单个表的单个分区中。 在可能的情况下,请使用实体组事务来批量执行插入、更新和删除操作。 使用实体组事务可减少客户端应用程序与服务器之间的往返操作次数、减少需要收费的事务数(一个实体组事务计为一个收费事务,最多可能包含 100 个存储操作),以及启用原子更新(实体组事务中的所有操作都成功或都失败)。 高延迟性的环境(例如移动设备)可以充分利用实体组事务。
Upsert
尽可能使用表的“Upsert”操作。 有两种类型的“Upsert”,两种都可能比传统的“插入”和“更新”操作更高效:
InsertOrMerge:若要上传实体的一部分属性,但不确定实体是否已存在,请使用此操作。 如果实体存在,则该调用会更新包含在 Upsert 操作中的属性,保留所有现有的属性不变,而如果实体不存在,则会插入新的实体。 这类似于在查询中使用投影,因为只需上传在更改的属性。
InsertOrReplace:若要上传全新实体,但不确定实体是否已存在,请使用此操作。 仅当知道这个刚上传的实体完全正确时才使用此操作,因为该实体会完全覆盖旧实体。 例如,需要更新用于存储用户当前位置的实体,而不管应用程序以前是否存储过该用户的位置数据;新位置实体是完整的,不需要任何旧实体提供的任何信息。
将数据系列存储在单个实体中
有时候,应用程序会存储一系列需要频繁进行一次性检索的数据:例如,应用程序可能会跟踪一段时间内的 CPU 使用情况,以便绘制过去 24 小时内数据的滚动图表。 一种方法是每小时构建一个表实体,每个实体代表一个具体的小时,并存储该小时的 CPU 使用情况。 为了针对该数据绘图,应用程序需要检索保留过去 24 小时内数据的实体。
此外,也可以让应用程序将每小时的 CPU 使用情况存储为单个实体的独立属性:更新每个小时的时候,应用程序可以使用单个“InsertOrMerge Upsert”调用来更新最近的一个小时的值。 针对数据进行绘图时,应用程序只需检索 1 个实体而非 24 个,这样的查询非常高效。 有关查询效率的详细信息,请参阅标题为查询范围的部分。
在 Blob 中存储结构化数据
如果需要在执行批量插入后再统一检索实体的范围,请考虑使用 Blob,而不要使用表。 日志文件就是一个很好的例子。 可以批处理几分钟的日志,插入这些日志,然后一次性检索好几分钟的日志。 在这种情况下,使用 Blob 要比使用表的性能更好,因为这可以大幅减少要写入或读取的对象数,并可能会减少需要发出的请求数。
表存储的可伸缩性和性能目标
标准存储帐户的可伸缩性和性能目标
状态和错误代码