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

暂时性故障处理

与远程服务和资源通信的所有应用程序都必须对暂时性故障敏感。 对于在云中运行的应用程序尤其如此,因为环境的性质和 Internet 上的连接性,这种故障可能会更频繁地遇到。 暂时性故障包括暂时失去与组件和服务网络连接、服务暂时不可用,以及服务繁忙时发生的超时。 这些故障通常会自我更正,因此,如果在适当延迟后重新进行操作,则很可能会成功。

本文提供有关暂时性故障处理的一般指南。

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

暂时性故障可能发生在任何环境、任何平台或作系统以及任何类型的应用程序中。 对于在本地基础结构上运行的解决方案,应用程序及其组件的性能和可用性通常通过昂贵且通常未使用的硬件冗余进行维护,组件和资源彼此靠近。 此方法使故障的可能性较低,但暂时性故障仍可能发生,如外部电源或网络问题或其他灾难方案等不可预见的事件导致的中断。

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

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

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

  • 应用程序与它使用的资源和服务之间通常有更多的硬件组件,包括网络基础结构(如路由器和负载均衡器)。 这种额外的基础结构偶尔可能会引入额外的连接延迟和暂时性连接故障。

  • 客户端和服务器之间的网络条件可能可变,尤其是在通信通过 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 中提供的内置或默认重试策略,但前提是它们适合你的方案。 这些策略通常是通用的。 在某些情况下,它们可能都是你需要的,但在其他情况下,它们不提供各种选项来满足你的特定要求。 若要确定最合适的值,需要执行测试以了解设置如何影响应用程序。

记录和跟踪暂时性和非过渡性故障

  • 在重试策略中包含异常处理,以及其他用于记录重试尝试的检测。 偶尔出现的暂时性故障和重试是正常的,并不表示存在问题。 但是,定期和不断增加的重试次数通常是导致故障或降低应用程序性能和可用性的问题的指标。

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

  • 请考虑在日志条目中存储一个值,该值指示重试是由服务限制或其他类型的故障(例如连接故障)引起的,以便在分析数据期间区分它们。 节流错误数量的增加往往是应用程序设计缺陷的迹象,或需要切换到提供专用硬件的高级服务。

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

  • 请考虑实现一个遥测和监控系统,当失败次数、失败率、平均重试次数或操作成功前所花费的总时间增加时,引发警报。

管理不断失败的操作

  • 请考虑如何处理在每次尝试中继续失败的操作。 这样的情况是不可避免的。

    • 尽管重试策略定义了应重试作的最大次数,但它不会阻止应用程序使用相同数量的重试再次重复该作。 例如,如果订单处理服务因严重错误而永久停止运行,重试策略可能会检测到连接超时,并将其视为暂时性故障。 代码重试操作指定次数,再然后放弃。 但是,当另一个客户下订单时,即使它每次都会失败,也会再次尝试这个操作。

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

    • 应用程序可以在间歇性的基础上定期测试服务,并在请求之间间隔较长,以检测服务何时可用。 适当的间隔取决于操作的重要性和服务性质等因素。 它可能在几分钟到几个小时之间。 测试成功后,应用程序可以恢复正常作,并将请求传递给新恢复的服务。

    • 同时,你可能能够回退到另一个服务实例(可能位于其他数据中心或应用程序中),使用提供兼容(可能更简单)功能的类似服务,或者根据希望服务能很快可用来执行一些替代操作。 例如,可能适合将服务请求存储在队列或数据存储中,并稍后重试。 或者,可以将用户重定向到应用程序的备用实例,降低应用程序的性能,但仍提供可接受的功能,或者只向用户返回一条消息以指示应用程序当前不可用。

其他注意事项

  • 在决定重试次数和策略重试间隔的值时,请考虑该服务或资源上的操作是否为长时间运行或多步骤操作的一部分。 当某个环节失败时,补偿已经成功的所有其他操作步骤可能很困难或成本高昂。 在这种情况下,只要该策略不通过持有或锁定稀缺资源来阻止其他操作,就可以接受较长的间隔和大量的重试。

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

  • 请考虑重试操作的范围。 例如,在包含多个操作的级别实现重试代码可能更容易,如果一个操作失败,可以对所有这些操作进行重试。 但是,这样做可能会导致幂等性问题或不必要的回滚操作。

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

  • 考虑重试策略如何影响共享应用程序中的邻居和其他租户,以及使用共享资源和服务时。 主动重试策略可能会导致这些其他用户和共享资源和服务的应用程序出现越来越多的暂时性故障。 同样,应用程序可能会受到资源和服务其他用户实施的重试策略的影响。 对于业务关键型应用程序,你可能想要使用未共享的高级服务。 这样做使你能够更好地控制这些资源和服务的负载以及因此而产生的限制,以帮助证明额外成本的合理性。