65.9K
CodeProject 正在变化。 阅读更多。
Home

理解指数退避重试模式和断路器模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (11投票s)

2016年10月7日

CPOL

8分钟阅读

viewsIcon

27388

在本文中,我们将讨论重试模式的重要性以及如何在我们的应用程序中有效实现它。

引言

在本文中,我们将讨论重试模式的重要性以及如何在我们的应用程序中有效实现它。我们还将讨论如何将指数退避和断路器模式与重试模式一起使用。这更多的是一篇理论文章,因为重试的实际实现将很大程度上取决于应用程序的需求。

背景

我们大多数人都在我们的应用程序中或多或少地实现过重试机制。为什么我们需要将此称为模式并长篇大论地讨论它,这是首先想到的问题。为了理解这一点,我们首先需要理解瞬态故障的概念,以及为什么在现代基于云的世界中,瞬态故障更值得考虑。

瞬态故障

瞬态故障是指在与外部组件或服务通信时发生的故障,并且该外部服务不可用。但这种不可用性或连接失败并非由于服务本身的问题,而是由于网络故障或服务器过载等原因。此类问题是短暂的。如果我们再次调用服务,很可能我们的调用会成功。这类故障称为瞬态故障。

传统上,我们在数据库连接和服务调用中遇到过此类错误。但在新的云世界中,由于我们的应用程序本身也可能有一些元素/组件在云中运行,所以发生此类错误的可能性增加了。我们的应用程序的不同部分可能分别托管在云上。因此,网络瞬时丢失、服务不可用和超时等故障变得更加突出(相对而言)。

这类故障可以通过简单地延迟后再次调用服务来轻松避免。

如何使用重试模式处理瞬态故障

在我们开始讨论如何处理瞬态故障之前,首要任务应该是识别瞬态故障。识别方法是检查故障是否是目标服务发送的,并且从应用程序的角度来看具有一些上下文。如果是这种情况,那么我们知道这不是瞬态故障,因为服务正在向我们发送故障。但是,如果我们收到的故障不是来自服务,而是可能来自其他原因,例如基础设施问题,并且故障似乎可以通过简单地再次调用服务来解决,那么我们可以将其归类为瞬态故障。

一旦我们将故障识别为瞬态故障,我们就需要设置一些重试逻辑,以便问题可以通过简单地再次调用服务来解决。重试的典型实现方式如下:

  1. 识别故障是否为瞬态故障。
  2. 定义最大重试次数。
  3. 重试服务调用并增加重试计数。
  4. 如果调用成功,将结果返回给调用者。
  5. 如果仍然收到相同的故障,则继续重试,直到达到最大重试次数。
  6. 如果即使在最大重试次数后调用仍然失败,则通知调用模块目标服务不可用。

简单重试的问题

我们在上一节讨论的重试机制非常直接,人们可能会想为什么我们要讨论如此简单的事情。我们大多数人在我们的应用程序中使用这种重试机制。我们讨论这个的原因是上述重试机制有一个小问题。

为了理解这个问题,让我们想象一个场景,瞬态故障发生是因为服务过载或服务端实施了某种节流。因此,此服务正在拒绝新的调用。这是一个瞬态故障,因为如果我们过一段时间再调用服务,我们的调用可能会成功。但也有可能我们的重试请求进一步增加了繁忙服务的过载。这意味着如果我们的应用程序有多个实例在重试相同的服务,则服务将长时间处于过载状态,并且需要更长的时间才能从该状态恢复。

所以从某种程度上说,我们的请求进一步促成了故障的原因,最终是我们的应用程序由于服务恢复时间更长而受到影响。那么我们如何解决这个问题呢?

指数退避前来救援

将指数退避与重试结合使用的想法是,我们不是在等待固定时间后重试,而是在每次重试失败后增加重试之间的等待时间。

例如,当请求第一次失败时,我们等待1秒后重试。如果第二次失败,我们等待2秒后进行下一次重试。如果第二次重试失败,我们等待4秒后进行下一次重试。因此,我们在每次失败后递增地增加连续重试请求之间的等待时间。这给服务一些喘息时间,以便如果故障是由于服务过载,它可以更快地解决。

注意:这些等待持续时间仅供参考。实际等待时间应取决于应用程序和服务。在实际场景中,用户等待请求处理的时间不会超过7秒(如上例所示)。

因此,通过指数退避,我们的重试算法将如下所示:

  1. 识别故障是否为瞬态故障
  2. 定义最大重试次数
  3. 重试服务调用并增加重试计数
  4. 如果调用成功,将结果返回给调用者
  5. 如果仍然收到相同的故障,则增加下一次重试的延迟时间
  6. 继续重试并不断增加延迟时间,直到达到最大重试次数
  7. 如果即使在最大重试次数后调用仍然失败,则通知调用模块目标服务不可用

带指数退避的重试示例用法

此模式的最佳实现示例是 Entity Framework。Entity Framework 提供连接弹性。连接弹性意味着 Entity Framework 能够使用重试模式自动重新建立断开的连接。

Entity Framework 有一个 `IDbExecutionStrategy` 接口,它负责连接重试机制。Entity Framework 提供了该接口的 4 种实现:`DefaultExecutionStrategy`、`DefaultSqlExecutionStrategy`、`DbExecutionStrategy` 和 `SqlAzureExecutionStrategy`。前两种策略,即 `DefaultExecutionStrategy` 和 `DefaultSqlExecutionStrategy` 在失败时根本不重试。另外两种,即 `DbExecutionStrategy` 和 `SqlAzureExecutionStrategy` 实现带指数退避的重试。在这些策略中,第一次重试会立即发生,如果失败,下一次重试会在延迟后发生。此延迟将呈指数级增加,直到达到最大重试次数。

关于长时间瞬态故障怎么办

我们还需要处理另一种情况,即瞬态故障持续时间很长。首先想到的问题是,瞬态故障怎么会持续很长时间,或者我们如何将持续很长时间的故障称为瞬态故障。故障的一个可能原因是网络问题,我们的应用程序将其识别为瞬态故障,但此连接问题需要比通常更长的时间才能恢复。在这种情况下,我们的应用程序认为故障是瞬态故障,但故障持续了很长时间。长时间瞬态故障的问题在于,我们的应用程序最终会不断重试、等待和重试,直到达到最大重试次数,从而浪费资源。因此,我们应该有一些机制来识别长时间瞬态故障,并在我们识别出故障为长时间瞬态故障时,让这种重试机制不起作用。

你好,断路器

断路器模式的目的是处理长时间的瞬态故障。这种模式背后的思想是,我们将服务调用封装在一个断路器中。如果重试失败次数超过某个阈值,我们将把这个电路置为 OPEN 状态,这意味着该服务当时不可用。一旦电路达到 OPEN 状态,对服务的后续调用将立即向调用者返回失败,而不是执行我们的重试逻辑。

此断路器将有一个超时期限,之后它将进入 HALF-OPEN 状态。在此状态下,它将允许一次服务调用,该调用将确定服务是否已变为可用。如果服务不可用,它将返回 OPEN 状态。如果服务在此超时后变为可用,则断路器将进入 CLOSED 状态。调用者将能够调用服务,并且通常的重试机制将再次发挥作用。

这将有助于我们避免在故障持续时间较长的情况下进行所有无用的重试执行,从而节省资源并向调用者提供更及时的反馈。

关注点

瞬态故障是不可避免的。在新的云世界中,我们应该更加谨慎地预测瞬态故障。在这篇小文章中,我们讨论了如何使用带指数退避的重试模式处理瞬态故障。我们研究了 Entity Framework 如何使用这种模式。我们还研究了如何使用断路器模式来处理长时间的瞬态故障。

有一点很重要,那就是我们不应该总是在我们的应用程序中盲目地实现指数退避和断路器。是否使用它们应该由应用程序需求和服务行为决定。本文是从初学者的角度编写的。希望这能提供一些信息。

历史

  • 2016 年 10 月 7 日:第一个版本
© . All rights reserved.