Azure开发了许多经过验证的做法,用于开发具有队列存储的高性能应用程序。 此查检表列出了开发人员在优化性能时可以遵循的关键做法。 在设计应用程序时以及在整个流程中,请牢记这些做法。
Azure 存储在容量、事务速率和带宽方面存在可伸缩性与性能目标。 有关Azure 存储可伸缩性目标的详细信息,请参阅标准存储帐户的可伸缩性和性能目标以及队列存储的性能目标。
清单
本文将经过验证的性能做法整理到开发队列存储应用程序时可以遵循的清单中。
| Done | Category | 设计注意事项 |
|---|---|---|
| 可伸缩性目标 | 能否将应用程序设计为使用的存储账户数不超过最大数量? | |
| 可伸缩性目标 | 您是否在避免接近容量和事务限制上限? | |
| 网络 | 客户端设备是否具有足够高的带宽和足够低的延迟,以实现所需的性能? | |
| 网络 | 客户端设备是否具有优质网络链接? | |
| 网络 | 客户端应用程序是否位于存储帐户所在的同一区域? | |
| 直接客户端访问 | 是否使用共享访问签名 (SAS) 和跨源资源共享 (CORS) 来实现对 Azure 存储的直接访问? | |
| .NET 配置 | 对于.NET框架应用程序,是否已将客户端配置为使用足够数量的并发连接? | |
| .NET 配置 | 对于 .NET Framework 应用程序,是否已将.NET配置为使用足够数量的线程? | |
| 并行度 | 是否能够确保对并行度进行适当的界定,使客户端功能不会过载或接近可伸缩性目标? | |
| Tools | 是否使用 Microsoft 提供的最新版客户端库和工具? | |
| 重试 | 您是否针对节流错误和超时采用了带指数退避的重试策略? | |
| 重试 | 对于不可重试的错误,应用程序是否会避免重试? | |
| 配置 | 是否关闭了 Nagle 的算法以提高小型请求的性能? | |
| 消息大小 | 消息是否紧凑,以提高队列的性能? | |
| 批量检索 | 是否在单个获取操作中检索多个消息? | |
| 轮询频率 | 你是否进行了足够频繁的轮询,以降低应用程序的感知延迟? | |
| 更新消息 | 是否执行更新消息操作以在处理消息中存储进度,以便在发生错误时避免重新处理整个消息? | |
| Architecture | 您是否通过使用队列,将长时间运行的工作负载移出关键路径,从而提高整个应用程序的可扩展性,并对其进行独立扩展? |
可伸缩性目标
如果应用程序接近或超过任何可扩展性目标,则可能会遇到事务延迟增加或被限流。 Azure 存储限制应用程序时,服务将开始返回 503 (Server Busy) 或 500 (Operation Timeout) 错误代码。 保持在可伸缩性目标限制范围内,以避免这些错误,是增强应用程序性能的重要组成部分。
有关队列存储的可伸缩性目标的详细信息,请参阅Azure 存储可伸缩性和性能目标。
最大存储帐户数
如果您接近达到某个特定订阅/区域组合允许的存储账户数量上限,是否正在通过使用多个存储账户进行分片,以提高入站吞吐量、出站吞吐量、每秒 I/O 操作次数(IOPS)或容量? 对于此方案,Azure 建议在可能的情况下,利用存储帐户的更高限制来减少工作负荷所需的存储帐户数。 若要请求提高存储帐户的限制,请联系 Azure 支持部门。
容量与事务目标
如果应用程序正接近单个存储帐户的可伸缩性目标,可考虑采用以下方法之一:
- 如果队列的可伸缩性目标不足以用于应用程序,请使用多个队列并跨队列分发消息。
- 重新考虑导致应用程序接近或超过可伸缩性目标的工作负载。 能否换一种设计方式,以减少带宽或容量的占用,或者减少交易次数?
- 如果应用程序肯定会超出伸缩性目标之一,请创建多个存储帐户并将应用程序数据跨多个这样的存储帐户进行分区。 如果使用这种模式,则在设计应用程序时,必须确保能够在以后添加更多的存储帐户,以便进行负载均衡。 存储帐户本身除了用于数据存储、事务处理或数据传输之外,并无其他开销。
- 如果应用程序接近带宽目标,请考虑压缩客户端的数据,以减少将数据发送到 Azure 存储所需的带宽。 虽然压缩数据可能会节省带宽并提高网络性能,但它也会对性能产生负面影响。 评估客户端数据压缩和解压缩的额外处理要求对性能造成的影响。 请记住,存储压缩的数据可能会使故障排除更加困难,因为使用标准工具查看数据可能更具挑战性。
- 如果您的应用程序接近可扩展性目标,请确保在重试时使用指数退避策略。 最好是尝试通过实施本文中所述的建议来避免达到可伸缩性目标。 但是,对重试使用指数退避会导致应用程序无法快速重试,从而导致限制问题恶化。 有关详细信息,请参阅 “超时和服务器繁忙错误 ”部分。
网络
应用程序的物理网络约束可能对性能产生重大影响。 以下部分介绍了用户可能会遇到的一些限制。
客户端网络功能
如以下各部分所述,网络链接的带宽和质量在应用程序性能方面发挥着重要作用。
Throughput
通常情况下,对带宽来说,问题在于客户端的功能。 较大的 Azure 实例的 NIC 具有较大的容量,因此如果需要提高单个计算机的网络限制,则应考虑使用较大的实例或更多 VM。 如果要从本地应用程序访问Azure 存储,则相同的规则适用:了解客户端设备的网络功能以及与Azure 存储位置的网络连接,并根据需要改进它们,或将应用程序设计为在其功能范围内工作。
链接质量
请注意,因错误和数据包丢失而导致的网络状况会降低有效吞吐量,使用任何网络都是这样。 使用 Wireshark 或网络监视器可能有助于诊断此问题。
Location
在任何分布式环境中,将客户端放置在服务器附近可提供最佳性能。 要以最低的延迟访问 Azure 存储,则最好是将客户端放置在同一 Azure 区域内。 例如,如果你有一个使用Azure 存储的 Azure Web 应用,请在单个区域(例如中国东部 2 或中国北部 2)中找到它们。 将资源放到一起可降低延迟和成本,因为在同一个区域使用带宽是免费的。
如果客户端应用程序访问Azure 存储但不托管在Azure(例如移动设备应用或本地企业服务)中,则在靠近这些客户端的区域中查找存储帐户可能会降低延迟。 如果客户端广泛分布在各地,请考虑在每个区域使用一个存储帐户。 如果应用程序存储的数据是特定于各个用户的,不需要在存储帐户之间复制数据,则此方法更容易实施。
SAS 和 CORS
假设你需要授权用户 Web 浏览器或手机应用中运行的代码(例如 JavaScript)访问 Azure 存储中的数据。 一种方法是构建充当代理的服务应用程序。 用户的设备将对服务进行身份验证,而后者又可授权访问 Azure 存储资源。 这样,就可以避免在不安全的设备上公开存储帐户密钥。 但是,此方法会明显增大服务应用程序的开销,因为在用户设备与 Azure 存储之间传输的所有数据必须通过服务应用程序。
使用共享访问签名 (SAS) 即可避免将服务应用程序用作 Azure 存储的代理。 使用 SAS 可让用户设备通过受限访问令牌直接对 Azure 存储发出请求。 例如,如果用户想要将照片上传到应用程序,则服务应用程序可以生成 SAS 并将其发送到用户的设备。 SAS 令牌可按指定的时间间隔授予写入 Azure 存储资源的权限,该时间过后,SAS 令牌将会过期。 有关 SAS 的详细信息,请参阅使用共享访问签名 (SAS) 授予对 Azure 存储资源的有限访问权限。
通常,Web 浏览器不允许在一个域中由网站托管的页面中的 JavaScript 对另一个域执行某些操作,例如写入操作。 此策略称为同源策略,可防止一个页面上的恶意脚本获取另一网页上的数据的访问权限。 但是,在云中构建解决方案时,同源策略可能会成为一种限制。 跨源资源共享 (CORS) 是一种浏览器功能,它使目标域能够与信任源自源域的请求的浏览器通信。
例如,假设 Azure 中运行的某个 Web 应用程序对 Azure 存储帐户发出了某个资源请求。 该 Web 应用程序是源域,存储帐户是目标域。 你可以为任意 Azure 存储服务配置 CORS,以向 Web 浏览器表明,来自源域的请求受到 Azure 存储的信任。 有关 CORS 的详细信息,请参阅 Azure 存储的跨源资源共享 (CORS) 支持。
SAS 和 CORS 都有助于避免 Web 应用程序上出现不必要的负载。
.NET 配置
对于使用 .NET Framework 的项目,本部分列出的某些快速配置设置可以用于显著提高性能。 如果使用 .NET 之外的语言,则需查看类似的概念是否适用于所选择的语言。
提高默认连接限制
注释
本部分适用于使用 .NET Framework 的项目,因为连接池由 ServicePointManager 类控制。 .NET Core 在连接池管理方面推出了重大更改,使连接池在 HttpClient 级别进行,默认情况下池大小不受限制。 这意味着 HTTP 连接会自动缩放以满足工作负载。 建议尽可能使用最新版本的 .NET,以利用增强的性能。
对于使用 .NET Framework 的项目,可以使用以下代码将默认连接限制(通常在客户端环境中为 2 或服务器环境中的 10)增加到 100。 通常情况下,应将值大致设置为应用程序使用的线程数。 在打开任何连接前设置连接限制。
ServicePointManager.DefaultConnectionLimit = 100; //(Or More)
若要详细了解 .NET Framework 中的连接池限制,请参阅 .NET Framework 连接池限制和新 Azure SDK for .NET。
对于其他编程语言,请参阅文档以确定如何设置连接限制。
增大最小线程数
如果将同步调用与异步任务一起使用,则可能需要增加线程池中的线程数:
ThreadPool.SetMinThreads(100,100); //(Determine the right number for your application)
有关详细信息,请参阅 ThreadPool.SetMinThreads 方法。
不受限制的并行度
虽然并行度有助于提高性能,但在使用不受限制的并行度时应保持谨慎,因为这意味着对线程数或并行请求数没有限制。 请务必限制上传或下载数据、访问同一存储帐户中的多个分区以及访问同一分区中的多个项的并行请求。 如果并行度不受限制,应用程序可能会超出客户端设备的处理能力或存储帐户的可扩展性目标,从而导致更高的延迟并触发限流。
客户端库和工具
为获得最佳性能,请始终使用 Microsoft 提供的最新客户端库和工具。 Azure 存储客户端库可用于各种语言。 Azure 存储还支持 PowerShell 和 Azure CLI。 Microsoft 正在积极开发这些客户端库和工具,并注重其性能,使用最新服务版本对其进行更新,确保这些工具可以在内部协调好许多经过证实的做法。 有关详细信息,请参阅Azure 存储参考文档。
处理服务错误
当服务无法处理请求时,Azure 存储会返回错误。 了解给定方案中Azure 存储可能返回的错误有助于优化性能。
超时和服务器繁忙错误
如果应用程序接近可伸缩性限制,Azure 存储可能会限制应用程序。 在某些情况下,由于某些暂时性条件,Azure 存储可能无法处理请求。 在这两种情况下,服务可能会返回 503 (Server Busy) 或 500 (Timeout) 错误。 如果服务正在对数据分区进行重新均衡以提高吞吐量,则也可能会发生这些错误。 通常,客户端应用程序应重试导致上述某种错误的操作。 但是,如果Azure 存储正在限制应用程序,因为它超出了可伸缩性目标,或者即使服务由于其他原因无法为请求提供服务,主动重试可能会使问题变得更糟。 建议使用指数退避重试策略,客户端库默认采用此行为。 例如,应用程序可能会在 2 秒、4 秒、10 秒、30 秒后重试,然后完全放弃。 这样,应用程序可显著降低对服务造成的负载,而不是加剧可能导致限流的行为。
连接错误可以立即重试,因为这类错误并非由限流导致,通常只是暂时性的。
不可重试的错误
客户端库在执行重试处理时,能够识别哪些错误可以重试,哪些错误不能重试。 但是,如果你是直接调用 Azure 存储 REST API,那么有些错误是不应重试的。 例如,400 (Bad Request) 错误指示客户端应用程序发送的请求无法处理,因为它未采用预期格式。 每次重新发送此请求都会导致相同的响应,因此没有必要重试。 如果直接调用 Azure 存储 REST API,请注意潜在错误以及是否应重试这些错误。
有关 Azure 存储错误代码的详细信息,请参阅状态和错误代码。
禁用 Nagle 算法
Nagle 的算法在 TCP/IP 网络中广泛实现,作为提高网络性能的手段。 但是,在所有情况下(如高度交互的环境)都不是最佳选择。 Nagle 的算法对Azure 表存储请求的性能产生负面影响,如果可能,则应禁用它。
消息大小
队列性能和可伸缩性随着消息大小的增加而减少。 仅将接收方所需的信息放入消息中。
批量检索
在单个操作中,最多可以从队列中检索 32 条消息。 批量检索可以减少客户端应用程序的往返次数,这对于具有高延迟的环境(如移动设备)尤其有用。
队列轮询间隔
大多数应用程序通过轮询从队列中获取消息,而该队列可能是该应用程序事务量的最大来源之一。 明智地选择轮询间隔:轮询过于频繁可能会导致应用程序接近队列的可伸缩性目标。 然而,按照 20 万次交易仅需 0.01 美元的价格(在撰写本文时),单个处理器以每秒一次的频率轮询一个月,成本还不到 15 美分,因此成本通常不会成为影响你选择轮询间隔的因素。
有关最新成本信息,请参阅 Azure 存储定价。
执行更新消息操作
可以执行更新消息操作来增加不可见超时或更新消息的状态信息。 此方法比将作业从一个队列传递到下一个队列的工作流更有效,因为作业的每个步骤都已完成。 应用程序可以将作业状态保存到消息中,然后继续处理,而不必在每个步骤完成后都将用于作业下一步的消息重新加入队列。 请记住,每个更新消息操作都计入可伸缩性目标。
应用程序体系结构
使用队列使应用程序体系结构可缩放。 下面列出了一些可以使用队列使应用程序更具可缩放性的方法:
- 你可以使用队列来积压待处理任务,以便进行处理,并平衡应用程序中的工作负载。 例如,可以将用户发出的请求排入队列,以执行处理器密集型工作,例如调整上传的图像的大小。
- 你可以使用队列将应用程序的各个部分解耦,从而对它们进行独立扩展。 例如,Web 前端可以将用户的调查结果放入队列中,以便以后进行分析和存储。 可以根据需要添加更多辅助角色实例来处理队列中的数据。