当前,Polly Retry策略将独立撤销所有失败的请求。因此,如果有10个请求失败,并且我设置了"永远重试"策略,那么每次重试时,它将再发送10个请求,服务器将永远不会恢复。
如何异步传递所有失败的请求,只重试一个请求,并在重试成功后恢复正常流程?
我不能(不想)使用断路器,因为我的服务是后台工作者服务,断路器破坏了整个后台服务逻辑。
// 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个请求。
问题
据我所知,您有一个HttpClient
,用于针对同一下游系统发出N个速率受限的并发请求。
您想要处理以下故障场景:
- 如果出现暂时的网络问题,您希望重试单个请求
- 如果下游系统过载(所以大多数并发请求都失败了),那么您需要后退,只使用一个请求来检测其健康状况
选项A-合并CB并重试
断路器策略起代理作用。它跟踪传出的通信,如果有太多连续的故障,则阻止进一步的请求。它通过抛出BrokenCircuitException
来缩短请求。
在一段时间后,CB将允许针对下游系统发出单个请求,如果成功,则允许所有传出通信,但如果失败,则会缩短它们。在这里我详细介绍了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注册为内部策略,将retry注册为外部策略,以便能够依赖升级。在这里,我已经详细介绍了这个确切的场景。
注意:如果需要,可以在断路器打开时使用不同的延迟。我在这里详细介绍了如何使用Context
对象来实现这一点。
选项B-使用队列
如果应用程序没有崩溃,上述解决方案可以正常工作。如果是这样,那么您必须从头开始整个处理过程。
如果你需要避免这种情况,那么你需要将你的工作项(待处理的url)存储在某个地方。
我建议采用以下架构:
- 您的主要工作程序不会针对下游系统发出http请求,而是创建作业/工作项
- 它可以将工作项存储在数据库或持久队列中
- 还有另一个工作者从数据库或队列中获取作业并尝试执行请求
- 如果请求成功,则会从永久存储中删除工作项
- 如果请求失败,那么它不会删除项目,而是获取一个新项目
- 根据您的要求,您可能需要删除项目并将其推送到队列的末尾<lt;有点重新排队
- 获取逻辑可以了解断路器状态
- 如果CB已关闭,则它将获取N个作业
- 如果它是Open,那么它只获取一个
使用此体系结构,您不需要显式重试策略,因为您的队列/数据库会保留那些未成功的项目。因此,您的获取逻辑将检索相同的作业,直到它最终完成。
您可以通过创建一个死信队列来进一步扩展这一概念,在该队列中可以存储失败N次的工作项。有了它,你的队伍就不会被";"永久";工作项。