如何避免分布式服务中的“重试风暴”

57次阅读
没有评论

问题描述

在使用分布式服务时,遇到了一个问题:在配置了重试策略的情况下,由于服务的正常操作中会发生数据包丢失,导致客户端在放弃之前重试一定次数。用户想知道如何避免这种“重试风暴”的问题。
用户提供了一个示例架构图,如下所示:
如何避免分布式服务中的“重试风暴”
假设整个服务的规模支持每秒80,000个请求,并且运行在80%的容量下。如果流量激增导致服务每秒接收到101,000个请求,将会有1,000个请求失败。当重试策略生效时,会产生额外的1,000+个请求,具体取决于故障检测的位置,这将使整个服务的请求量达到102,000个每秒,从而导致服务陷入死循环,每秒失败的请求数量翻倍。
除了大规模超额配置服务(这样做效率低下)之外,用户想知道还有哪些策略可以避免“重试风暴”。

解决方案

请注意以下操作注意版本差异及修改前做好备份。

方案1

如果你试图避免任何关键服务的中断(例如,如果我的API调用未能适当地提供服务将导致人员死亡),那么你需要预算大量资源来解决由于过度配置而带来的巨大低效率问题。是的,这些资源必须是专用的,不能允许出现流量激增的情况,否则多个服务的激增将导致服务中断。
在更为常见的情况下,如果服务中断只是不方便,你可以从客户端和服务器两方面解决问题。虽然值得注意的是,从逻辑上讲,实际上无法解决流量过大的问题,因为如果不处理流量(这会消耗资源),你无法知道它是重试、是客户端错误地处理了成功的请求、还是DDOS攻击等。但你可以减轻影响。
客户端代码中编写合理的重试逻辑,设置上限并具备优雅失败的机制。这样,你的用户不会陷入无限循环的失败请求中,而是会收到一个错误提示,告诉他们在稍后再试一次。
对于服务器端基础设施,最简单的解决方案是进行限流。对请求进行硬限制,特别是如果你可以根据特定的用例逻辑进行逻辑分布(例如,如果你有一个集中式服务,做出一些重要决策,你想要开始阻塞地理位置较远的请求,这可能导致服务器端线程挂起?或者你想要均匀分布你不可避免的但是微小的中断?等等),基本上就是返回一个有意的503错误,比让请求通过然后发送504错误要便宜得多。基本上,根据你当前能够提供的内容强制客户端行为,并提供正确的响应,以便客户端可以做出适当的反应。

方案2

使用退避机制可以防止这些重试风暴。
根据Google App Engine的《设计规模》指南中的“实现重试的退避机制”部分,防止这些重试风暴的一种方法是使用退避机制。
大多数GAE API已经默认启用了这样的退避机制/策略。
以下是一个示例,展示了如何在重试时实现退避机制:

import time
import random

def retry_with_backoff():
    max_retries = 5
    retries = 0
    while retries < max_retries:
        try:
            # 执行请求
            response = make_request()
            # 处理响应
            handle_response(response)
            break
        except Exception as e:
            # 处理异常
            handle_exception(e)
            # 退避等待
            wait_time = 2 ** retries + random.random()
            time.sleep(wait_time)
            retries += 1
    else:
        # 达到最大重试次数后处理失败
        handle_failure()

在上面的示例中,我们使用了一个retry_with_backoff函数来执行请求,并在遇到异常时进行退避等待。我们设置了最大重试次数为5次,并使用指数退避策略来计算等待时间。在每次重试之前,我们使用random.random()函数来引入一些随机性,以避免所有请求同时发生。
请注意,这只是一个示例,你可以根据自己的需求进行调整和扩展。

方案3

使用负载均衡和自动扩展来处理流量峰值。
另一种方法是使用负载均衡和自动扩展来处理流量峰值。通过将流量分散到多个实例上,并根据需要自动扩展实例数量,可以有效地处理大量请求。
以下是一些常见的负载均衡和自动扩展解决方案:
– 使用云服务提供商的负载均衡器和自动扩展功能,如AWS Elastic Load Balancer和Auto Scaling。
– 使用容器编排工具,如Kubernetes和Docker Swarm,来管理容器化应用程序的负载均衡和自动扩展。
– 使用CDN(内容分发网络)来缓存静态内容,并将动态请求路由到最近的服务器。
这些解决方案可以根据你的具体需求进行调整和配置,以实现高可用性和可扩展性。

方案4

使用消息队列来缓冲和控制流量。
另一种方法是使用消息队列来缓冲和控制流量。将请求放入消息队列中,并使用消费者来处理请求。通过调整消费者的数量和速率,可以有效地控制流量,并避免“重试风暴”。
以下是一些常见的消息队列解决方案:
– RabbitMQ:一个功能强大的开源消息队列系统,支持多种消息传递模式。
– Apache Kafka:一个高吞吐量的分布式消息队列系统,适用于大规模数据流处理。
– AWS SQS:一个托管的消息队列服务,可在亚马逊云上进行快速和可靠的消息传递。
这些消息队列解决方案可以根据你的具体需求进行配置和扩展,以实现可靠的消息传递和流量控制。

总结

在分布式服务中,避免“重试风暴”是一个复杂的问题,没有一种通用的解决方案。根据你的具体需求和环境,可以选择适合你的策略,如预算超额配置、退避机制、负载均衡和自动扩展、消息队列等。通过合理的设计和配置,可以减轻“重试风暴”带来的影响,并提高服务的可用性和可靠性。

正文完