【问题标题】:Polly Circuit Breaker policy and HttpClient with ASP.NET Core APIPolly 断路器策略和 HttpClient 与 ASP.NET Core API
【发布时间】:2018-12-31 11:41:18
【问题描述】:

我在结合HttpClient 设置 Polly 的 CircuitBreaker 时遇到问题。

具体来说,CircuitBreakerHttpClient 用于 ASP.NET Core Web API 控制器,链接如下:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests

https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory

下面是我想要的

  • 重试策略:如果出现瞬时错误,每个请求重试3次。

  • cicuit Breaker 策略:如果所有请求都发生五个瞬时错误,则生效。

问题

虽然重试策略正常工作,但断路器策略不起作用。

在 _httpClient.SendAsync() 发生 5 次异常后,CarController 仍会收到请求,并且不会暂停 30 秒(控制器会立即处理请求)。

HandledEventsAllowedBeforeBreaking:5

DurationOfBreakInSeconds:30

我错过了什么吗?

配置服务

配置 Polly 重试和断路器策略,并指定自定义 HttpClient,HttpClientService

    public void ConfigureServices(IServiceCollection services)
        {
           services.AddHttpClient();
           services.AddHttpClient<IHttpClientService, HttpClientService>()                                
                        .AddPolicyHandler((service, request) =>
                            HttpPolicyExtensions.HandleTransientHttpError()
                                .WaitAndRetryAsync(3,
                                    retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)),
                onRetry: (outcome, timespan, retryCount, context) =>
                                {
                                    service.GetService<ILog>().Error("Delaying for {delay}ms, then making retry {retry}.",
                                        timespan.TotalMilliseconds, retryCount);
                                }
                            )
)
                        )
                       .AddPolicyHandler((service, request) =>                      
                                HttpPolicyExtensions.HandleTransientHttpError()
                                    //Further external requests are blocked for 30 seconds if five failed attempts occur sequentially.
                                    //Circuit breaker policies are stateful.All calls through this client share the same circuit state.
                                    .CircuitBreakerAsync(5,
                                                 TimeSpan.FromSeconds(30), 
                                                 (result, timeSpan, context)=>
                                                            service.GetService<ILog>().Error("CircuitBreaker onBreak for {delay}ms", timeSpan.TotalMilliseconds),
                                                  context =>
                                                      service.GetService<ILog>().Error("CircuitBreaker onReset")));

         }

汽车控制器

IHttpClientServiceConfigureServices 的 Polly 策略中指定。 HttpClientService 使用 HttpClient

断路器不工作:即使发生来自_httpClient.SendAsync() 的五个瞬态错误(例如HttpRequestException),CarController 仍然可以接收请求,并且不会暂停 30 秒。

 [ApiVersion("1")]
    [Route("api/v{version:apiVersion}/[controller]")]
    [ApiController]
    public class CarController : ControllerBase
    {
        private readonly ILog _logger;
        private readonly IHttpClientService _httpClientService;
        private readonly IOptions<Config> _config;

        public CarController(ILog logger, IHttpClientService httpClientService, IOptions<Config> config)
        {
            _logger = logger;
            _httpClientService = httpClientService;
            _config = config;
        }

        [HttpPost]
        public async Task<ActionResult> Post()
        {  

            using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
            {
                string body = reader.ReadToEnd();

                    var statusCode = await _httpClientService.PostAsync(
                        "url",
                        new Dictionary<string, string>
                        {
                            {"headerID", "Id"}                           
                        },
                        body);
                    return StatusCode((int)statusCode);               
            }
        }
      }

HttpClientService

似乎 HttpClient 在请求之间没有状态。

断路器不工作:即使发生来自_httpClient.SendAsync() 的五个瞬态错误(例如HttpRequestException),CarController 仍然可以接收请求,并且不会暂停 30 秒。

 public class HttpClientService
{
    private readonly HttpClient _httpClient;
    public HttpClientService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<HttpStatusCode> PostAsync(string url, Dictionary<string, string> headers, string body)
    {
        using (var content = new StringContent(body, Encoding.UTF8, "application/json"))
        {
            foreach (var keyValue in headers)
            {
                content.Headers.Add(keyValue.Key, keyValue.Value);
            }

             var request = new HttpRequestMessage(HttpMethod.Post, url)
                                    {
                                        Content = content
                                    };

            var response = await _httpClient.SendAsync(request);

            response.EnsureSuccessStatusCode();
            return response.StatusCode;
        }

    }

ASP.NET Core API 2.2

更新 更新了 SetWaitAndRetryPolicy 扩展方法以使用 IServiceProvider。

【问题讨论】:

  • 我猜问题是因为你调用了两次AddHttpClient,但你没有与它共享熔断策略(也不是WaitAndRetryAsync)。我不太确定您创建 IHttpClientService.. 的方式。您不应该在注册时为其设置基地址吗?
  • 另请注意:当您达到异常限制(在您的情况下为 3)时,状态机将移动到 Open 状态。不过,这不会阻止您的控制器被调用。控制器本身可以被调用,但Http 调用将快速失败,BrokenCircuitException 将保持这种状态 30 秒。 30 秒后,电路将移动到HalfOpen,将第一个呼叫作为试验,并决定它是回到Open 还是恢复正常Closed。看看这里github.com/App-vNext/Polly/wiki/…

标签: c# asp.net-core .net-core asp.net-core-webapi polly


【解决方案1】:

断路器策略为stateful to track failure rates across calls,因此需要为long-lived rather than created per request

贴出的代码中对HttpClientFactory的重载使用方式:

.AddPolicyHandler((service, request) => HttpPolicyExtensions.HandleTransientHttpError()
    .CircuitBreakerAsync( /* etc */

manufacturing an instance of the circuit-breaker per request,因此断路器永远没有时间建立故障状态。

那个重载(和类似的)是designed for selecting policies dynamically based on characteristics of the request。但实际上发布的代码是(编辑:最初发布的代码是)没有使用 service, request 输入参数,所以你可以删除 (service, request) =&gt; 部分,并使用 HttpClientFactory 上的重载,它需要一个策略实例:

.AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError()
    .WaitAndRetryAsync(/* etc */))
.AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError()
    .CircuitBreakerAsync(/* etc */))

HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */) 返回的单个、长期存在的断路器实例将被配置为使用它的 HttpClient 实例使用。


旁注(特别是对于任何想要在给定过载情况下使用断路器的读者):

可以在 HttpClientFactory 中使用带有请求驱动的policySelector 重载的断路器。只需确保 lambda 表达式选择单个实例,而不是每次请求都生成一个新实例。例如:

var circuitBreaker = HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */);
services.AddHttpClient<IHttpClientService, HttpClientService>()                                
    .AddPolicyHandler((service, request) => circuitBreaker); // By way of example technique: more typically with this overload, there is some more complex logic to select different policies for different kinds of request.

编辑以在 cmets 中回答问题: 该实例不必声明为 static 以使其长期存在。根据上面的代码示例,它可以在使用之前在 Startup.ConfigureServices(...) 方法中声明。 lambda 并在 HttpClientFactory 上对其进行配置将捕获它并使其长期存在。

circuitBreaker 实例应为 shared across calls you want to break in common。如果您将断路器附加到通过 HttpClientFactory 声明的特定 HttpClient 配置,则通过该 HttpClient 配置的实例的所有调用稍后由 DI 从 HttpClientFactory 检索,将共享断路器,从而共同中断。

在将断路器与 HttpClientFactory 一起使用时,这通常意味着您可以在 HttpClientFactory 上声明一个 HttpClient 配置(类型化或命名),您希望对其进行共同的断路器调用。


旁注: 还选择了断路器的变体triggers based on consecutive fault count。 (提到以防万一一个额外的因素;发布的问题是指跨请求发生的 5 个错误,但不是特别连续。)

【讨论】:

  • 我确实需要使用请求驱动的 policySelector 重载,以便我可以通过代码使用日志记录:service.GetService().Error()。请参阅我更新的 OP。当您说“断路器的单个实例”时,您是指整个应用程序中的单个实例吗?在这种情况下,我们需要将 CircuitBreaker 作为 Startup 类中的静态属性?
  • 一个断路器实例应该在您想要共同中断的调用之间共享:请参阅github.com/App-vNext/Polly/wiki/…
  • 扩展答案以更详细地回答后续 cmets。
  • @mountaintraveller - 是否有实际将服务 (IServiceProvider) 传递给 circuitBreakerAsync 以便我们获得 ILogger 实例?
猜你喜欢
  • 2020-10-29
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多