何时重试、退避和抖动有效?

别再盲目Retry!本文揭秘RetryExponential BackoffJitter的真实效果。高并发下,仅靠它们无法解决服务过载。需结合服务TPS限制,考虑新请求涌入,才能避免Retry Storm。云原生架构需谨慎评估,对症下药!

译自:When Do Retry, Backoff, and Jitter Work?

作者:Tejas Ghadge

在最近与工程师的一次讨论中,我遇到了一个常见的误解,即重试、指数退避、抖动等方法如何在服务负载增加时帮助维持服务的可用性。我很高兴了解到工程师们对不同类型的退避、抖动和重试有着深刻的理论知识。

但令我惊讶的是,许多人认为这些重试、退避和抖动是可靠处理服务器负载增加的完美解药。虽然重试、退避和抖动是非常棒的,而且是处理突发负载情况的廉价解决方案,但在假设这是一个通用疗法之前,需要理解一个重要的区别。

最好先理解这些方法如何以及为何有效,然后利用这些知识来应用和验证它们在不同情况下是否有效。

假设服务的介绍

让我们假设我们有一个 Deals Service,用于计算适用于给定产品的折扣。为了简单起见,我们将假设 Deals Service 每秒可以处理两个并发请求。以下是 Deals Service 的一个简单图表。

图表 1.

让我们假设我们正在进行一次闪购,并且有五个用户对优惠感兴趣。因此,当促销开始时,他们都向我们的 Deals Service 发出并发请求。请耐心听我说;有些人可能会说这在现实世界中不是这样运作的。鉴于五个请求远高于 Deals Service 可以处理的请求,它开始过载。发生这种情况是因为服务器上的资源(如 CPU、内存、网络 I/O 等)有限,并且随着请求的增加,对这些资源的争用也会增加。随着延迟的增加,这通常会在服务器端表现出来,并且客户端请求开始超时。

图表 2 显示了客户看到的 TPS(X 轴)增加与延迟(Y 轴)之间的关系。为简单起见,我们将假设客户端超时为一秒。

图表 2.

请注意,随着并发 TPS 增长超过 2(我们在图表 1 中为 Deals Service 建立的安全限制),延迟增长超过 1 秒的客户端超时,从而导致请求超时。请注意,服务性能在超过两个请求的安全限制后会迅速下降,而且在几乎所有情况下,现实世界的服务也是如此,因此如果您正在面试,这是一个值得记住的有用图表。

注意:为了识别这些安全限制(例如,在我们的 Deals Service 案例中,TPS 为 2),我们需要以一定的频率持续对服务进行负载测试,并确保每个服务器资源和服务指标都在安全限制范围内。例如,CPU 在 2 TPS 时不会超过 60% 的使用率,但在 3 TPS 时会增加到 90% 的 CPU,等等。

我们如何解决这个问题?

当客户端看到超时时,我们通常依赖重试。重试是处理所有分布式系统遇到的瞬时故障(即,像数据包丢失这样的网络问题等)的强大解决方案。重试具有大多数人没有考虑到的副作用(例如,如果重试是幂等的,我们会创建重试风暴吗?等等)。为了简单起见,我们不会在这里深入探讨这一点,我将在另一篇文章中介绍这一点。现在,让我们实现一个没有任何延迟的重试。

在 Deals Service 的情况下,假设我们的所有五个请求在第一次尝试后都超时,我们将立即向 Deals Service 发送相同的五个请求激增,从而导致相同的结果。因此,添加重试没有帮助。

重试与指数退避的解药怎么样?

指数退避(或不同类型的退避)在每次重试失败后呈指数增加等待时间。等待时间通常与重试尝试计数成正比(许多指数退避会限制总等待时间)。鉴于所有五个请求,Deals Service 将同时超时,因此指数重试将导致与不等待重试相同的结果。因此,与不等待重试相比,退避没有太大帮助。

重试与指数退避和抖动怎么样?

让我们假设一个假设的抖动公式 — rand (1,3) — 我们选择一个介于 1 到 3 秒之间的随机值。我们将这个随机抖动值添加到指数退避值中,并获得一个新的等待时间,然后再向 Deals Service 发出后续请求。图 3 显示了在发出第一个请求后,所有五个请求都失败的情况。

图 3.

例如,Customer 1 计算出 1 秒的随机抖动值,并将其添加到指数计算值 1 秒,以获得 2 秒的等待时间,然后再发出后续请求。同样,Customer 2 计算出 2 秒的抖动值,并将其添加到 1 秒的指数退避中,以等待 3 秒,然后再向 Deals Service 发出以下请求。

正如我们所看到的,向指数退避添加抖动有助于分散请求的突发,从而导致所有请求在下一次尝试中都成功的情况。

注意:我们方便地选择了一个抖动值,该值有助于将请求分散到 Deals Service,并将并发请求保持在 2 TPS 以下。但是,在实际场景中,一些新客户可能会非常容易地发送新请求,从而使 Deals Service 超过 2 TPS 的临界点,并使我们平滑突发的工作变得毫无用处。让我们看看下面的细节。

抖动和指数退避何时不起作用?

在 Deals Service 的示例中,我们假设客户总数保持恒定为五个(即,我们系统中的固定客户数)。相反,如果我们始终每秒添加一个新客户(超出这五个客户),从而向 Deals Service 发出新的第一个请求,那么我们的退避和抖动解决方案将不起作用。这提出了关于抖动和指数退避有效性的一个重要观点。如果我们不断地向系统中添加新客户,他们不会意识到其他客户正在退避,试图减少我们服务上的并发工作负载。因此,即使退避/抖动尝试通过将先前的负载延迟到将来来分散服务上的先前负载,系统上的总体并发负载仍会继续增加。

结束语

在使用重试时,带有抖动的退避是平滑短时请求突发的有效机制。但是,如果我们看到增加服务并发负载的新客户请求,它们的总体有效性就会降低。如果并发负载超过临界点(例如,在我们的 Deals Service 案例中为 2 的 TPS),则退避和抖动根本没有帮助。我希望这能使开发人员了解指数退避和抖动在实际情况中如何工作,以及需要存在哪些假设才能使它们在分布式系统中有所帮助。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注