Политика Polly Circuit Breaker и HttpClient с ASP.NET Core API

У меня проблемы с настройкой CircuitBreaker Полли в комбинации HttpClient,

В частности, CircuitBreaker а также HttpClient используются для ASP.NET Core Web API Controller по ссылкам ниже:

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

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

Ниже то, что я хочу

  • Повторите политику: повторите 3 раза для каждого запроса, если есть временная ошибка.

  • Политика прерывателя cicuit: вступает в силу, если во всех запросах возникают временные ошибки.

Эта проблема

Хотя политика повторных попыток работает правильно, политика прерывателей не работает.

CarController по-прежнему получает запрос после 5 исключений из _httpClient.SendAsync() и не делает паузу в течение 30 секунд (запросы обрабатываются контроллером немедленно).

HandledEventsAllowedBeforeBreaking: 5

DurationOfBreakInSeconds: 30

Я что-то здесь упускаю?

ConfigureServices

Настройте политики повторных попыток 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")));

         }

CarController

IHttpClientService указано в политике Полли в ConfigureServices, HttpClientService использования HttpClient,

Автоматический выключатель не работает: даже после пяти переходных ошибок (например, HttpRequestException) от _httpClient.SendAsync()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 не отслеживает состояние запросов.

Автоматический выключатель не работает: даже после пяти переходных ошибок (например, HttpRequestException) от _httpClient.SendAsync()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.

1 ответ

Политики автоматического выключателя отслеживают частоту отказов по вызовам, поэтому они должны быть долговечными, а не создаваться для каждого запроса.

Способ перегрузки HttpClientFactory в размещенном коде используется:

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

изготавливает экземпляр автоматического выключателя по запросу, поэтому автоматические выключатели никогда не успевают создать состояние отказа.

Эта перегрузка (и аналогичные) предназначены для динамического выбора политик на основе характеристик запроса. Но на самом деле опубликованный код (РЕДАКТИРОВАТЬ: изначально опубликованный код был) не использует service, request входные параметры, так что вы можете просто удалить (service, request) => часть, и использовать перегрузку на HttpClientFactory, которая принимает экземпляр политики:

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

Этот единственный долгоживущий экземпляр выключателя, возвращенный HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */) затем будут использоваться экземплярами HttpClient, для которых HttpClientFactory был настроен для его использования.


Sidenote (особенно для любых читателей, которые хотят использовать автоматический выключатель с заданными перегрузками):

Можно использовать автоматический выключатель с управлением по запросу policySelector перегрузки в HttpClientFactory. Просто необходимо убедиться, что лямбда-выражение выбирает один экземпляр, а не то, что каждый раз создается новый экземпляр для каждого запроса. Например:

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.

РЕДАКТИРОВАТЬ, чтобы ответить на вопрос в комментариях: этот экземпляр не должен быть объявлен static сделать это долгожителем. Это может быть объявлено в рамках Startup.ConfigureServices(...) метод, непосредственно перед использованием, как в примере кода выше. Лямбда и ее настройка на HttpClientFactory захватят ее и сделают ее долговечной.

circuitBreaker Экземпляр должен быть разделен между вызовами, которые вы хотите сделать общими. Если вы подключите выключатель к определенному HttpClient Конфигурация объявлена ​​через HttpClientFactory, все вызовы через экземпляры этого HttpClient Конфигурация, позднее полученная из HttpClientFactory DI, будет использовать CircuBreaker и, таким образом, будет иметь общие черты.

При использовании автоматического выключателя с HttpClientFactory это обычно означает, что вы можете объявить его HttpClient Конфигурация (набираемая или именованная) в HttpClientFactory для каждой подсистемы, для которой вы хотите, чтобы общие вызовы прерывались.


Sidenote: Выбранный вариант автоматического выключателя также срабатывает на основе количества последовательных неисправностей. (Упоминается на всякий случай дополнительный фактор; опубликованный вопрос относится к 5 ошибкам, возникающим между запросами, но не конкретно последовательно.)

Другие вопросы по тегам