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

重试风暴反模式

当服务变得不可用或繁忙时,频繁的客户端重试可能会阻止服务恢复并恶化问题。 无限期重试无效,因为请求通常仅在有限的时间内保持有效。

上下文和问题

在云中,服务有时会遇到问题,并变得对客户端不可用或强制实施限制或速率限制。 虽然客户端重试到服务的失败连接是一种很好的做法,但它们不应过于频繁或太长时间地重试。 短时间内重试不太可能成功,因为服务可能尚未恢复。 恢复期间的连接尝试过多可能会使服务不堪重负,并加剧原始问题。 这种情况有时被称为 惊群效应

以下示例演示了客户端连接到基于服务器的 API 的方案。 如果请求未成功,客户端会立即重试,并始终重试。 此类行为通常比此示例更微妙,但同样的原则适用。

public async Task<string> GetDataFromServer()
{
    while(true)
    {
        var result = await httpClient.GetAsync(string.Format("http://{0}:8080/api/...", hostName));
        if (result.IsSuccessStatusCode) break;
    }

    // ... Process result.
}

解决方案

客户端应用程序应遵循最佳做法来防止重试风暴。

  • 限制重试次数和持续时间。 while(true)编写循环似乎很简单,但通常不想长时间重试。 导致启动请求的条件可能会更改。 大多数应用程序只需重试几秒钟或几分钟。

  • 在重试尝试之间暂停并增加等待时间。 如果服务不可用,则立即重试不太可能成功。 逐渐增加尝试之间的时间量,例如,使用指数退避策略。

  • 正常处理服务故障。 如果服务未响应,请确定是否中止尝试,并向组件的用户或调用方返回错误。 设计应用程序时,请考虑这些失败方案。

  • 使用 断路器模式 此模式专为帮助避免重试风暴而设计。

  • 遵循响应标头。 如果服务器提供 retry-after 响应标头,则在指定的时间段过后不要尝试重试。

  • 使用官方 SDK 与 Azure 服务通信。 这些 SDK 通常具有内置的重试策略和保护措施,以防止引发重试风暴。 如果与没有 SDK 或 SDK 的服务通信不当处理重试逻辑,请考虑使用 Polly for .NET 等库,或者 重试 JavaScript 以正确处理重试逻辑,并避免自行编写代码。

  • 当可用时,请使用抽象层。 如果使用支持它的环境,请使用服务网格或其他抽象层发送出站调用。 通常,这些工具(如 Dapr)支持重试策略,并自动遵循最佳做法,例如在重复尝试后退让。 此方法无需自行编写重试代码。

  • 考虑批处理请求,并在可用时使用请求池。 许多 SDK 代表你处理请求批处理和连接池,这减少了应用程序所尝试的出站连接尝试总数。 但请避免过于频繁地重试这些连接。

服务还应保护自己免受重试风暴的冲击。

  • 添加网关层以在事件期间阻止连接。 此方法遵循 Bulkhead 模式。 Azure 为不同类型的解决方案提供许多网关服务,包括 Azure Front DoorAzure 应用程序网关Azure API 管理

  • 限制网关的请求。 此方法可防止后端组件因请求过多而不知所措。

  • 向客户端发送信号。 限制时,发送回 retry-after 标头以帮助客户端了解何时重新尝试其连接。 客户端不需要遵循这些标头,但许多人这样做。

注意事项

  • 了解错误类型。 某些错误类型不指示服务失败,而是指示来自客户端的请求无效。 例如,如果客户端应用程序收到错误响应,则重试同一 400 Bad Request 请求可能会无济于事,因为服务器已通知你请求无效。

  • 定义适当的重试持续时间。 客户端应考虑重新尝试连接的最适当的时间长度。 时间范围应符合业务需求,并能够合理地将错误返回给用户或调用方。 大多数应用程序只需重试几秒钟或几分钟。

检测问题

从客户端的角度来看,此问题的症状可能包括很长的响应或处理时间,以及指示反复尝试重试连接的遥测数据。

从服务的角度来看,此问题的症状可能包括在短时间内从一个客户端发出的多个请求,或从单个客户端恢复中断时发出的多个请求。 在发生故障后,服务也可能难以恢复。 或者,在故障修复后,服务可能存在正在进行的级联故障。

示例诊断

以下部分演示了从客户端和服务角度检测潜在重试风暴的一种方法。

使用客户端遥测标识模式

Application Insights 记录来自应用程序的遥测数据,并使这些数据可用于查询和可视化。 它将出站连接跟踪为依赖项,并允许用户访问和绘制此信息的图表,以确定客户端何时向同一服务发出多个出站请求。

以下屏幕截图显示了 Application Insights 门户中“指标”选项卡上的图形。 它显示按远程依赖项名称拆分的依赖项故障指标。 此方案在短时间内尝试到依赖项的连接失败超过 21,000 次。

Application Insights 的屏幕截图,显示 30 分钟内单个依赖项的 21,000 个依赖项失败。

使用服务器遥测标识模式

服务器应用程序可能能够检测来自单个客户端的大量连接。 在以下示例中,Azure Front Door 充当应用程序的网关,并配置为将所有请求 记录 到 Log Analytics 工作区。

若要识别在最后一天向应用程序发送大量请求的客户端 IP 地址,请在 Log Analytics 中运行以下 Kusto 查询。

AzureDiagnostics
| where ResourceType == "FRONTDOORS" and Category == "FrontdoorAccessLog"
| where TimeGenerated > ago(1d)
| summarize count() by bin(TimeGenerated, 1h), clientIp_s
| order by count_ desc

如果在重试风暴期间运行此查询,则会显示来自单个 IP 地址的大量连接尝试。

Log Analytics 的屏幕截图,其中显示了一小时内从单个 IP 地址到 Azure Front Door 的 81,608 个入站连接。