Политика Polly Circuit Breaker и HttpClient с ASP.NET Core API - PullRequest
0 голосов
/ 31 декабря 2018

У меня проблемы с настройкой 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 раза для каждого запросаесли есть временная ошибка.

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

Проблема

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

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 указывается в политике Polly в 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 Ответ

0 голосов
/ 01 января 2019

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

Способиспользуется перегрузка 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 ошибкам, возникающим в запросах, но не по порядку.)

...