【问题标题】:Polly Retry - Pass all execution until a retry is successfulPolly Retry - 传递所有执行,直到重试成功
【发布时间】:2021-12-18 18:15:14
【问题描述】:

目前,Polly Retry 策略会独立停用所有失败的请求。因此,如果有 10 个请求失败并且我设置了永远重试策略,那么每次重试发生时它会再发送 10 个请求,并且服务器永远不会恢复。

如何异步传递所有失败的请求,只重试一个请求,重试成功后恢复正常流程?

我不能(不想)使用 Circuit Breaker,因为我的服务是后台工作人员服务,而 Circuit Breaker 会破坏整个后台服务逻辑。

// Current code with only retry policy
var retry = HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryForeverAsync(retryNo => new TimeSpan(0, retryNo > 3 ? 10 : (retryNo * 2), 0));
builder.Services.AddHttpClient<TestClient>().AddPolicyHandler(retry);

用例:我编写了一个后台服务,并不断抓取一个包含 30000 多个页面的网站。为了防止网站过载,我使用SemaphoreSlim(或 Bulkhead)来限制在某个时间点发送到服务器的请求数。

不过,服务器仍有可能拒绝我的请求。那时,我需要重试只有一个失败的请求单元,服务器再次开始接受我的请求。由于我同时发送多个请求,Polly 正在重试所有失败的请求,这使服务器不满意。

期望:

10 个请求失败 -> 重试 1 个请求(单元成功) -> 如果成功则重新发送剩余的 9 个请求。

【问题讨论】:

  • 我不确定我是否理解您在寻找什么。你想分散重试吗?如果是这样,请使用带抖动的指数回退。
  • 或者您正在寻找断路器替代方案?请提供更多信息以更好地了解您的问题?
  • @PeterCsala 我已经用正确的用例更新了帖子
  • 谢谢,现在我了解了用例。我不明白的是避免使用断路器。 CB 是一种工具,用于检测过载的下游系统并在它试图自我修复时停止泛滥。
  • 好的,今天晚些时候我会写一篇文章,详细介绍两个选项:A) 使重试策略 CB 感知 B) 使用队列来存储剩余的工作项

标签: c# polly


【解决方案1】:

问题

据我了解,您有一个 HttpClient,用于向同一下游系统发出 N 个速率受限的并发请求。

您要处理以下失败场景:

  • 如果出现暂时性网络问题,您希望重试单个请求
  • 如果下游系统过载(因此大多数并发请求都失败),那么您希望退出并仅使用单个请求来探测其健康状况

选项 A - 组合 CB 并重试

断路器策略充当代理。它跟踪传出的通信,如果有太多的连续失败,那么它会阻止进一步的请求。它通过抛出BrokenCircuitException 来缩短请求来实现这一点。

一段时间后,CB 将允许单个请求向下游系统发出请求,如果成功则允许所有传出通信,但如果失败则将其缩短。 Here我已经详细介绍了CB是如何工作的。

您可以调整重试策略以了解此异常。这意味着您的重试请求仍会发出,但不会离开您的应用程序域。幸运的是,在 Polly 中,您可以为一个策略定义多个触发器:

HttpPolicyExtensions
   .HandleTransientHttpError()
   .Or<BrokenCircuitException>()
   .WaitAndRetryForeverAsync(retryNo => new TimeSpan(0, retryNo > 3 ? 10 : (retryNo * 2), 0));

所以,无论是HttpRequestException 还是BrokenCircuitException,它都会触发。如果HttpStatusCode 是 408 或 5xx,它也会触发。

现在剩下的是将重试和断路器策略组合成一个弹性策略。您可以使用以下方法之一来做到这一点:

.AddPolicyHandler(retryPolicy.Wrap(cbPolicy))
//OR
.AddPolicyHandler(Policy.Wrap(retryPolicy, cbPolicy))

请注意订购。将 cb 注册为内部策略并将重试注册为外部策略以便能够依赖 escalation 非常重要。 Here我已经详细说明了这个确切的场景。

注意:如果您愿意,可以在断路器打开时使用不同的延迟。我有 detailed here 你如何使用 Context 对象来做到这一点。


选项 B - 使用队列

如果应用程序没有崩溃,上述解决方案可以正常工作。如果是这样,那么您必须从头开始整个处理。

如果您需要避免这种情况,那么您需要将您的工作项(待处理的 url)存储在某个地方。

我建议采用以下架构:

  • 您的主要工作人员不会向下游系统发出 http 请求,而是创建作业/工作项
    • 它可以将工作项存储在数据库或持久队列中
  • 还有另一个工作人员从数据库或队列中获取作业并尝试执行请求
    • 如果请求成功,则会从持久存储中删除工作项
    • 如果请求失败,它不会删除该项目,而是获取一个新项目
      • 根据您的要求,您可能需要删除该项目并将其推送到队列的末尾
  • 获取逻辑可以感知断路器状态
    • 如果 CB 已关闭,则它会获取 N 个作业
    • 如果它是 Open 则它只获取一个

使用这种架构,您不需要明确的重试策略,因为您的队列/数据库会保留那些未成功的项目。因此,您的 fetch 逻辑将检索相同的作业,直到最终完成。

您可以通过创建dead letter queue 来进一步扩展此概念,您可以在其中存储那些失败 N 次的工作项。这样,您的队列就不会被“永久”工作项污染。

【讨论】:

  • 哇,太棒了,按预期工作。
  • @VijayNirmal 你指的是选项A吗?
  • 是的,我尝试了一个示例应用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多