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

暂时性故障处理

与远程服务和资源通信的所有应用程序必须对暂时性故障敏感。 对于云中运行的应用程序尤其如此,因为其环境的性质与通过 Internet 建立连接的特点,更容易遇到这种类型的故障。 暂时性故障包括组件和服务瞬间断开网络连接、服务暂时不可用,以及当服务繁忙时出现超时。 这些故障通常可自我纠正,因此,如果在适当的延迟后重复操作,则操作可能会成功。

本文提供有关处理暂时性故障的一般指导。 有关处理使用 Azure 服务时发生的暂时性故障的信息,请参阅 Azure 服务的重试指南

为什么云中会出现暂时性故障?

任何环境、任何平台或操作系统以及任何类型的应用程序都会发生暂时性故障。 在本地基础结构上运行的解决方案中,应用程序及其组件的性能与可用性通常是通过昂贵但通常很少使用的硬件冗余来维持的,并且组件与资源的位置互相靠近。 此方法可以大大减少故障,但仍可能发生暂时性故障,由于外部电源或网络问题等不可预见事件或其他灾难状况造成的故障也可能发生。

云托管(包括私有云系统)可以通过跨许多商品计算节点使用共享资源、冗余、自动故障转移和动态资源分配,提供更高的整体可用性。 但是,由于云环境的性质,更可能发生暂时性故障。 原因包括:

  • 云环境中的许多资源是共享的,为了保护这些资源,会限制对这些资源的访问。 某些服务在负载上升到特定级别时,或到达吞吐量比率的上限时,会拒绝连接以便处理现有的请求,并为所有用户维持服务性能。 限制有助于为共享资源的邻居与其他租户维持服务质量。

  • 云环境使用大量商用硬件单元。 云环境将负载动态分散到多个计算单元和基础结构组件以提供性能, 并通过自动回收或更换故障单元来提供可靠性。 由于这种动态性,可能偶尔会发生暂时性故障和暂时连接失败。

  • 在应用程序与资源及其使用的服务之间,通常有多个硬件组件,包括网络基础结构,如路由器和负载均衡器。 这个附加的基础结构偶尔会导致额外的连接延迟与暂时性连接故障。

  • 客户端与服务器之间的网络状况会不时改变,尤其是通过 Internet 通信时。 即使在本地位置,高流量负载也可能减慢通信速度,并会导致间歇性的连接故障。

挑战

暂时性故障会对应用程序的实际可用性造成重大影响,即使应用程序已在所有可预测的条件下经过全面的测试。 要确保云托管的应用程序可靠运行,需要确保应用程序能够应对以下挑战:

  • 应用程序必须能够检测到故障的发生,并确定这些故障可能是暂时性的、持久性的还是终端故障。 发生故障时,不同的资源可能返回不同的响应,这些响应可能会根据操作上下文而有所不同。 例如,应用程序从存储读取时所发生错误返回的响应可能与写入存储时所发生错误返回的响应不同。 许多资源和服务都妥善制定了暂时性故障的合约。 但是,若不提供此类信息,则很难发现故障的性质,以及故障是否是暂时性的。

  • 如果确定故障可能是暂时性的,应用程序必须能够重试操作。 它还需要跟踪操作的重试次数。

  • 应用程序必须使用适当的重试策略。 此策略指定应用程序应该重试的次数、每次尝试之间的延迟时间,以及尝试失败后执行的操作。 适当的尝试次数以及每次尝试之间的延迟时间通常难以确定。 该策略将根据资源类型以及资源和应用程序的当前操作条件而有所不同。

一般指南

以下准则有助于为应用程序设计合适的暂时性故障处理机制。

确定是否存在内置的重试机制

  • 许多服务提供 SDK 或包含暂时性故障处理机制的客户端库。 服务使用的重试策略通常是根据目标服务的性质和要求定制的。 或者,对于确定重试是否适当,以及在下一次尝试重试之前要等待多长时间方面,服务的 REST 接口可能会返回有用的信息。

  • 除非有具体且明确的要求需要其他更适当的重试行为,否则应适时使用提供的内置重试机制。

确定操作是否适合重试

  • 只在故障是暂时性(通常可由故障的性质来判断),以及在重试时操作至少有一定成功的可能性时,才执行重试操作。 如果操作是无效的操作,例如数据库所更新的项不存在,或者请求的服务或资源发生灾难性错误,则重试操作没有意义。

  • 一般而言,只有在能够确定这样做的全部影响,且充分了解状况并可验证这些状况时,才实施重试。 否则,应该由调用代码来实施重试。 请记住,从无法控制的资源与服务返回的错误可能会随着时间而演进,可能需要重新访问暂时性故障检测逻辑。

  • 创建服务或组件时,请考虑实施错误代码和消息,以帮助客户端确定是否应重试失败的操作。 具体而言,指明客户端是否应重试操作(也许是通过返回 isTransient 值),并建议下一次重试之前的适当延迟。 如果要构建 Web 服务,请考虑返回服务合约中定义的自定义错误。 即使一般的客户端可能无法读取这些错误,但在创建自定义客户端时,这些错误很有帮助。

确定适当的重试计数与间隔

  • 优化用例类型的重试计数和间隔。 如果未重试足够的次数,应用程序可能无法完成该操作,并且可能会失败。 如果重试次数过多或间隔太短,应用程序可能会长期保留资源(例如线程、连接和内存),这会对应用程序的运行状况造成不利影响。

  • 根据操作类型调整时间间隔与重试次数值。 例如,如果操作是用户交互的一部分,则间隔应该较短,且只需重试几次。 使用此方法可以避免让用户等待响应,这会让连接保持打开,并可能会降低其他用户的可用性。 如果操作是长时间运行或重要工作流的一部分,取消并重新启动过程代价高昂或耗时,则每次尝试后等待的时间应较长,并且应重试更多次。

  • 请注意,确定适当的重试间隔是设计一个成功的策略时最困难的部分。 典型的策略会使用以下类型的重试间隔:

    • 指数退让。 应用程序在第一次重试之前短暂地等待,这样每个后续重试的间隔时间会呈指数增加。 例如,在 3 秒、12 秒、30 秒后重试操作。

    • 增量间隔。 应用程序在第一次重试之前短暂地等待,这样每个后续重试的间隔时间会按增量递增。 例如,在 3 秒、7 秒、13 秒后重试操作。

    • 固定间隔。 应用程序每次尝试的间隔时间相同。 例如,固定每 3 秒重试操作。

    • 立即重试。 有时候暂时性故障很短暂,可能是由于网络数据包冲突或硬件组件流量高峰等事件。 在此情况下,适合立即重试操作,因为如果故障在操作让应用程序组合并发送下一个请求时已清除,则操作可能会成功。 不过,立即重试次数不得超过一次。 如果立即重试失败,应改用备用策略,例如指数退避或回退操作。

    • 随机化。 任何上述重试策略都可包含随机化,以防止客户端的多个实例同时发送后续重试请求。 例如,一个实例可能会在 3 秒、11 秒、28 秒等时段后重试操作,而另一个实例则在 4 秒、12 秒、26 秒等时段后重试操作。 随机化是有用的技术,可配合其他策略使用。

  • 一般指导原则是,为后台操作使用指数退避策略,为交互式操作使用立即或固定间隔重试策略。 在上述两种情况下,应该选择延迟与重试计数,使所有重试的延迟上限都在所需的端到端延迟要求范围内。

  • 请考虑到所有会对重试操作的整体超时上限造成影响的因素组合。 这些因素包括失败连接生成响应所花费的时间(通常根据客户端的超时值设置),以及重试之间的延迟和重试次数上限。 所有这些时间的总和会导致整体操作时间变得漫长,尤其是在使用指数退让策略时,因为每次故障后重试的间隔时间会快速增加。 如果某个过程必须符合特定的服务级别协议 (SLA),则整体操作时间(包括所有的超时和延迟)必须在 SLA 定义的限制范围之内。

  • 不要实施过于激进的重试策略。 这些策略的间隔过短或重试过于频繁, 可能会对目标资源或服务产生不利影响。 这些策略可能会造成资源或服务无法从过载状态恢复,并且会继续阻止或拒绝请求。 这种情况会导致恶性循环,越来越多的请求将发送到资源或服务, 因而造成其恢复能力进一步降低。

  • 选择重试间隔时请考虑操作的超时,以避免立即启动后续尝试(例如当超时期限与重试间隔类似时)。 还应考虑是否需要让可能的总时间(超时值加重试间隔)短于特定的时间总和。 如果操作的超时时间过短或过长,则超时可能会影响等待时间,以及重试操作的频率。

  • 使用异常类型及其包含的任何数据,或者使用从服务返回的错误代码与消息,来优化重试次数和间隔。 例如,某些异常或错误代码(如 HTTP 代码 503 - 服务不可用,以及响应中的 Retry-After 标头)会指示错误可能持续的时间,或服务失败且不会响应任何后续尝试。

避免反模式

  • 在大多数情况下,避免使用包含重复重试代码层的实现。 避免使用包括级联重试机制的设计,或避免使用在涉及请求层次结构的操作的每个阶段实施重试的设计,除非有特定的要求需要这样做。 在这些例外的情况下下,请使用策略避免过多的重试次数和延迟期间过长,并确保了解后果。 例如,假设某个组件对另一个组件发出请求,后者再访问目标服务。 如果要对这两个调用各实施重试三次,则总共会对该服务重试九次。 许多服务和资源实施内置重试机制。 如果需要在更高级别实施重试,应研究如何禁用或修改这些机制。

  • 切勿实施永不结束的重试机制。 这样做可能会导致资源或服务无法从过载情况下恢复,并造成限制与遭到拒绝的连接持续更长时间。 使用有限的重试次数或使用断路器等模式,使服务可以恢复。

  • 切勿多次执行立即重试。

  • 在访问 Azure 中的服务与资源时,避免使用固定重试间隔,尤其是要重试很多次时。 此情况下的最佳方法是指数退避策略以及断路功能。

  • 防止同一个客户端有多个实例,或不同客户端有多个实例同时发送重试请求。 如果这种情况有可能发生,请在重试间隔中引入随机化。

测试重试策略与实施

  • 在尽可能广泛的条件下全面测试重试策略,尤其是当使用的应用程序与目标资源或服务要承受极端负载时。 要检查测试期间的行为,可以:

    • 将暂时性与非暂时性故障注入服务中。 例如,发送无效请求或添加代码用于检测包含不同错误类型的测试请求与响应。 有关使用 TestApi 的示例,请参阅使用 TestApi 进行故障注入测试TestApi 简介 – 第 5 部分:托管的代码故障注入 API

    • 创建资源或服务模型,用于返回真实服务可能返回的错误范围。 覆盖重试策略旨在检测的所有错误类型。

    • 对于创建和部署的自定义服务,通过暂时禁用或重载该服务来强制发生暂时性错误。 (不应尝试使 Azure 中的任何共享资源或共享服务重载。)

    • 对于基于 HTTP 的 API,请考虑在自动化测试中使用库来更改 HTTP 请求的结果,方法是增加额外的往返时间或更改响应(例如 HTTP 状态代码、标头、正文或其他因素)。 对于暂时性故障和其他类型的故障,这样做可以确定性地测试一部分故障状况。

    • 执行高负载因子和并发测试,确保重试机制与策略在这些条件下能正常工作。 这些测试还有助于确保重试不会对客户端的操作产生不利影响,也不会在请求之间造成交叉污染。

管理重试策略配置

  • 重试策略是重试策略所有元素的组合。 它定义了能确定故障是否可能是暂时性的检测机制、使用的间隔类型(例如固定、指数退避及随机化)、实际间隔值,以及重试次数。

  • 在应用程序中的多个位置实施重试,即使是最简单的应用程序也是如此,至于较复杂的应用程序,则必须每一层都实施。 考虑使用集中点存储所有策略,而不是将每个策略的元素硬编码在多个位置。 例如,在应用程序配置文件中存储间隔和重试计数等值、在运行时读取这些值,并以编程方式构建重试策略。 这样做可以更轻松地管理设置以及修改和优化值,以应对不断变化的要求和方案。 但是,请将系统设计为存储值而不是每次都要重新读取配置文件,并在无法从配置中获取值时使用适当的默认值。

  • 考虑在 Azure 云服务应用程序中存储值,用于在服务配置文件中构建运行时的重试策略,使这些值不需要重新启动应用程序即可更改。

  • 利用所用客户端 API 中的内置或默认重试策略,但前提是这些策略适合方案。 这些策略往往是通用的。 在某些状况下,这些策略可能都是必需的,但在其他状况下,它们可能无法提供完整的选项范围来满足特定的要求。 要确定最合适的值,需要执行测试来了解设置如何影响应用程序。

记录和跟踪暂时性与非暂时性故障

  • 在重试策略中包含异常处理,以及其他用于记录重试尝试的检测。 偶尔会出现暂时性故障和重试,这并不表示存在问题。 但是,固定递增的重试次数通常表示出现了可能会造成故障或降低应用程序性能与可用性的问题。

  • 将暂时性故障记录为警告项,而不是错误项,以便监视系统不会将其检测为应用程序错误而触发误报。

  • 考虑将值存储在日志项中,用于指示重试是由服务中的限制所造成,还是由其他类型的故障(例如连接失败)造成,使你能够在分析数据期间进行区分。 限制错误数目的增加通常表示应用程序的设计有瑕疵,或者需要改用可提供专用硬件的高级服务。

  • 考虑测量和记录包含重试机制的操作的总运行时间。 此指标是暂时性故障对用户响应时间、进程延迟以及应用程序用例效率造成的整体影响的良好指标。 此外,还要记录发生重试的次数,以便了解影响响应时间的因素。

  • 考虑实施遥测和监视系统,当故障次数与比率、重试平均次数或操作成功所需的整体时间增加时,该系统会引发警报。

管理不断失败的操作

  • 考虑如何处理每次尝试都失败的操作。 这种情况是不可避免的。

    • 尽管重试策略会定义操作应重试次数的上限,但它不会防止应用程序使用与重试次数相同的次数一再重复操作。 例如,如果订单处理服务因为严重错误而失败且永久失效,则重试策略会检测连接超时,并将它视为暂时性故障。 代码将按指定的次数重试操作,然后放弃。 但是,当另一位客户下单时,会再次尝试该操作,即使该操作每次都会失败。

    • 为防止不断重试连续失败的操作,应考虑实施断路器模式。 使用此模式时,如果在指定的时段内失败次数超过阈值,则会立即将请求返回给调用方,并将失败视为故障,而不会尝试访问失败的资源或服务。

    • 应用程序将定期测试服务,并间歇性地(请求之间的间隔非常长)检测服务何时可供使用。 适当的间隔取决于操作的重要性和服务的性质等因素。 它可能是几分钟到几小时之间之间的任何时间。 测试成功时,应用程序将恢复正常操作,并将请求传递给刚刚恢复的服务。

    • 同时,可以故障回复到服务的另一个实例(也许在不同的数据中心或应用程序中)、使用提供兼容(也许是更简单)功能的类似服务,或执行某些替代操作,以期该服务很快可供使用。 例如,有时适合将服务的请求存储在队列或数据存储中,供以后重试。 也可以将用户重定向到应用程序的其他实例、使应用程序性能降级但仍可提供可接受的功能,或者只是将消息返回给用户,指出应用程序当前不可用。

其他注意事项

  • 确定重试次数的值和策略的重试间隔时,请考虑服务或资源的操作是否是长时间运行或多步骤操作的一部分。 当某个操作步骤失败时,补偿其他所有操作步骤可能会很困难或代价非凡。 在此情况下,可以接受很长的间隔及大量的重试次数,前提是该策略不会因为保留或锁定稀缺资源而阻止其他操作。

  • 考虑重试相同的操作是否会导致数据不一致。 如果多步骤过程中的某些部分是重复的,并且操作不是幂等的,则可能会出现不一致情况。 例如,递增值的操作如果重复,则会生成无效的结果。 如果使用者无法检测到重复消息,则重复执行将消息发送到队列的操作可能会对消息使用者造成不一致情况。 为避免这些情况,请将每个步骤设计成幂等操作。 有关详细信息,请参阅幂等模式

  • 考虑重试操作的范围。 例如,可以更轻松地在包含数个操作的级别实施重试代码,如果其中一个操作失败,则重试所有操作。 但是,这可能会导致幂等性问题或不必要的回滚操作。

  • 如果选择的重试范围包含多个操作,在确定重试间隔时、监视操作运行时间时,以及因失败而引发警报之前,请考虑所有操作的延迟总和。

  • 考虑重试策略如何影响共享应用程序中的邻居或其他租户,以及何时使用共享资源与服务。 积极重试策略会导致其他用户以及共享资源与服务的应用程序发生越来越多的暂时性故障。 同样地,应用程序可能会受到资源与服务的其他用户所实施的重试策略的影响。 对于业务关键型应用程序,可能需要使用非共享的高级服务。 这样做可以更好地控制这些资源与服务的负载与后续限制,从而找到提高成本的理由。