Polly 和多个 HttpClients

Polly and Multiple HttpClients

我想将 Polly 与我的 HttpClientFactory 结合使用(在我的 C# .NET 5.0 项目中)。

但我遇到的问题是我的工厂中有多个命名的 HttpClients,而我想要实现的是当我有一个特定的状态代码时,例如404 我想使用我工厂的 另一个名为 HttpClient 的 重试(指向不同的服务器)。

但我看到人们经常使用 services.AddHttpClient(name, configureOptions).AddPolicyHandler() 模式。但那是行不通的,因为那不会调用另一个命名的 HttpClient。

我还想在 408 发生时“为他们自己”重试两个客户端。

是否有任何模式或示例代码? 我不知道最好的前进方向是什么。

我认为您可以通过以下方式实现所需的行为。

让我们注册两个命名的 http 客户端,在出现 408 状态代码时重试:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("ServiceA")
        .AddPolicyHandler(GetRetryPolicy("ServiceA"));

    services.AddHttpClient("ServiceB")
        .AddPolicyHandler(GetRetryPolicy("ServiceB"));

    //...
}

private IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(string name)
    => Policy<HttpResponseMessage>
    .HandleResult(res => res.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
    .WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(200),
    onRetryAsync: (dr, ts) => { Console.WriteLine($"Retry by {name}"); return Task.CompletedTask; });

我提供了一个 onRetryAsync 用于调试目的,只是为了查看重试何时执行。


现在让我们用一个简单的 webapi 控制器来连接东西:

[Route("api/[controller]")]
public class HomeController : Controller
{
    private readonly IHttpClientFactory clientFactory;
    public HomeController(IHttpClientFactory clientFactory)
    {
        this.clientFactory = clientFactory;
    }

    [HttpGet]
    public async Task<IActionResult> Get(int expectedStatusCode)
    {
        var result = await GetAsync(GetPrimaryProxy(), expectedStatusCode);
        if(result != null && result.StatusCode == System.Net.HttpStatusCode.NotFound)
            result = await GetAsync(GetSecondaryProxy(), expectedStatusCode);

        return StatusCode(500, result);
    }

    private HttpClient GetPrimaryProxy() => clientFactory.CreateClient("ServiceA");
    private HttpClient GetSecondaryProxy() => clientFactory.CreateClient("ServiceB");

    private async Task<HttpResponseMessage> GetAsync(HttpClient client, int expectedStatusCode)
        => await client.GetAsync($"https://httpstat.us/{expectedStatusCode}");
}
  • 我已经使用 https://httpstat.us 网站来模拟预期的响应状态代码
  • 很遗憾,您必须手动进行故障转移。无法以检查 HttpResponseMessage 和 returns 和 HttpClient.
  • 的方式设置回退策略

/api/Home/404

调试日志:

System.Net.Http.HttpClient.ServiceA.LogicalHandler: Information: Start processing HTTP request GET https://httpstat.us/404
info: System.Net.Http.HttpClient.ServiceA.LogicalHandler[100]
      Start processing HTTP request GET https://httpstat.us/404
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[100]
      Sending HTTP request GET https://httpstat.us/404
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Sending HTTP request GET https://httpstat.us/404
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[101]
      Received HTTP response headers after 319.9439ms - 404
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Received HTTP response headers after 319.9439ms - 404
info: System.Net.Http.HttpClient.ServiceA.LogicalHandler[101]
      End processing HTTP request after 326.1409ms - 404
System.Net.Http.HttpClient.ServiceA.LogicalHandler: Information: End processing HTTP request after 326.1409ms - 404

info: System.Net.Http.HttpClient.ServiceB.LogicalHandler[100]
      Start processing HTTP request GET https://httpstat.us/404
System.Net.Http.HttpClient.ServiceB.LogicalHandler: Information: Start processing HTTP request GET https://httpstat.us/404
info: System.Net.Http.HttpClient.ServiceB.ClientHandler[100]
      Sending HTTP request GET https://httpstat.us/404
System.Net.Http.HttpClient.ServiceB.ClientHandler: Information: Sending HTTP request GET https://httpstat.us/404
info: System.Net.Http.HttpClient.ServiceB.ClientHandler[101]
      Received HTTP response headers after 344.5937ms - 404
System.Net.Http.HttpClient.ServiceB.ClientHandler: Information: Received HTTP response headers after 344.5937ms - 404
info: System.Net.Http.HttpClient.ServiceB.LogicalHandler[101]
      End processing HTTP request after 350.932ms - 404
System.Net.Http.HttpClient.ServiceB.LogicalHandler: Information: End processing HTTP request after 350.932ms - 404
  • ServiceA发出请求,收到404
    • Retry 未触发,因为仅处理 408
  • ServiceB发出请求,收到404
    • Retry 未触发,因为仅处理 408

/api/Home/408

调试日志:

info: System.Net.Http.HttpClient.ServiceA.LogicalHandler[100]
      Start processing HTTP request GET https://httpstat.us/408
System.Net.Http.HttpClient.ServiceA.LogicalHandler: Information: Start processing HTTP request GET https://httpstat.us/408
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[100]
      Sending HTTP request GET https://httpstat.us/408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Sending HTTP request GET https://httpstat.us/408
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[101]
      Received HTTP response headers after 343.5167ms - 408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Received HTTP response headers after 343.5167ms - 408

Retry by ServiceA
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[100]
      Sending HTTP request GET https://httpstat.us/408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Sending HTTP request GET https://httpstat.us/408
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[101]
      Received HTTP response headers after 236.9796ms - 408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Received HTTP response headers after 236.9796ms - 408

Retry by ServiceA
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[100]
      Sending HTTP request GET https://httpstat.us/408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Sending HTTP request GET https://httpstat.us/408
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[101]
      Received HTTP response headers after 207.2602ms - 408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Received HTTP response headers after 207.2602ms - 408

Retry by ServiceA
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[100]
      Sending HTTP request GET https://httpstat.us/408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Sending HTTP request GET https://httpstat.us/408
info: System.Net.Http.HttpClient.ServiceA.ClientHandler[101]
      Received HTTP response headers after 203.3911ms - 408
System.Net.Http.HttpClient.ServiceA.ClientHandler: Information: Received HTTP response headers after 203.3911ms - 408
System.Net.Http.HttpClient.ServiceA.LogicalHandler: Information: End processing HTTP request after 1618.0826ms - 408
info: System.Net.Http.HttpClient.ServiceA.LogicalHandler[101]
      End processing HTTP request after 1618.0826ms - 408
  • ServiceA发出请求,收到408
    • Retry 在 200 毫秒延迟后触发
  • ServiceA发出请求,收到408
    • Retry 在 200 毫秒延迟后触发
  • ServiceA发出请求,收到408
    • Retry 在 200 毫秒延迟后触发
  • ServiceA发出请求,收到408
    • Retry 未触发,因为超过了最大重试次数
  • ServiceB 未被调用,因为状态代码是 408 而不是 404

我最终没有使用 PollyHttpClientBuilderExtensions.AddPolicyHandler 但我用它作为灵感来编写我自己的:https://github.com/App-vNext/Polly-Samples/blob/master/PollyDemos/Async/AsyncDemo08_Wrap-Fallback-WaitAndRetry-CircuitBreaker.cs