简体   繁体   English

Polly Circuit Breaker策略和具有ASP.NET Core API的HttpClient

[英]Polly Circuit Breaker policy and HttpClient with ASP.NET Core API

I have problems setting up Polly's CircuitBreaker in combination of HttpClient . 我在结合HttpClient设置Polly的CircuitBreaker时遇到问题。

Specifically, CircuitBreaker and HttpClient are used for ASP.NET Core Web API Controller following the links below: 具体来说,下面的链接将CircuitBreakerHttpClient用于ASP.NET Core Web API Controller:

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

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

Below is what I want 以下是我想要的

  • retry policy: retry 3 times for each request if there are transient error. 重试策略:如果有暂时性错误,则每个请求重试3次。

  • cicuit breaker policy: take effect if fives transient errors occur across all requests. cicuit breaker策略:如果所有请求中发生5个瞬态错误,则该策略生效。

The problem 问题

Although retry policy works correctly, circuit breaker policy does not work. 尽管重试策略正确运行,但断路器策略不起作用。

The CarController still receives request after 5 exceptions from _httpClient.SendAsync() occurred and doesn't pause for 30 seconds (requests are processed immeidately by the controller). 在发生_httpClient.SendAsync()的5个异常后,CarController仍接收请求,并且不会暂停30秒(请求由控制器立即处理)。

HandledEventsAllowedBeforeBreaking: 5 打破之前的HandledEventsAllowedBefore:5

DurationOfBreakInSeconds: 30 DurationBreakInSeconds:30

Am I missing something here? 我在这里想念什么吗?

ConfigureServices ConfigureServices

Configure Polly retry and circuit breaker policies, and sepecify custom HttpClient, HttpClientService 配置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 CarController

IHttpClientService is specified in Polly policy in ConfigureServices . IHttpClientServiceConfigureServices Polly策略中指定。 HttpClientService uses HttpClient . HttpClientService使用HttpClient

Circuit breaker does not work: even after five transient error occurs (eg HttpRequestException ) from _httpClient.SendAsync() , CarController can still receive request, and doesn't pause for 30 seconds. 断路器不起作用:即使在发生_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 HttpClientService

It seems HttpClient is not stateful across requests. 似乎HttpClient在请求中不是有状态的。

Circuit breaker does not work: even after five transient error occurs (eg HttpRequestException ) from _httpClient.SendAsync() , CarController can still receive request, and doesn't pause for 30 seconds. 断路器不起作用:即使在发生_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 ASP.NET Core API 2.2

Update Updated SetWaitAndRetryPolicy extension method to use IServiceProvider. 更新更新的SetWaitAndRetryPolicy扩展方法以使用IServiceProvider。

Circuit-breaker policies are stateful to track failure rates across calls , and so need to be long-lived rather than created per request . 断路器策略是有状态的,可跟踪呼叫之间的故障率 ,因此需要长期存在,而不是根据请求创建

The way the overload on HttpClientFactory in the code posted is used: 在发布的代码中使用HttpClientFactory重载的方式:

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

is manufacturing an instance of the circuit-breaker per request , so the circuit-breakers never have time to build up a failure state. 正在根据请求制造断路器的实例 ,因此断路器永远没有时间建立故障状态。

That overload (and similars) are designed for selecting policies dynamically based on characteristics of the request . 该重载(及类似内容) 旨在根据请求的特征动态选择策略 But in fact the posted code is (EDIT: the originally posted code was) not making any use of the service, request input parameters, so you can just delete the (service, request) => part, and make use of an overload on HttpClientFactory which takes a policy instance: 但是实际上,发布的代码是(编辑:原始发布的代码是)不使用service, request输入参数的,因此您可以删除(service, request) =>部分,并利用重载带有策略实例的HttpClientFactory:

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

That single, long-lived instance of circuit-breaker returned by HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */) will then be used by the instances of HttpClient for which HttpClientFactory was configured to use it. HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */)返回的HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */)断路器的单个长期实例将由配置了HttpClientFactory的HttpClient实例使用。


Sidenote (especially for any readers who do want to use circuit-breaker with the given overloads): 旁注 (特别是对于那些想在给定过载下使用断路器的读者):

It is possible to use circuit-breaker with the request-driven policySelector overloads in HttpClientFactory. 可以在HttpClientFactory policySelector断路器与请求驱动的policySelector重载一起使用。 It is just necessary to ensure that a single instance is selected by the lambda expression, not that a new instance is manufactured each time per request. 仅需要确保通过lambda表达式选择单个实例,而不是每次请求每次都制造一个新实例。 For example: 例如:

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.

EDIT to answer question in comments: That instance does not have to be declared static to make it long-lived. 编辑以在注释中回答问题:不必将该实例声明为static即可使其具有长寿命。 It can be declared within the Startup.ConfigureServices(...) method, immediately before use, as per the code example above. 根据上面的代码示例,可以在使用前立即在Startup.ConfigureServices(...)方法中声明它。 The lambda and configuring it on HttpClientFactory will capture it and make it long-lived. lambda并在HttpClientFactory上配置它会捕获它并使它长寿。

The circuitBreaker instance should be shared across calls you want to break in common . circuitBreaker实例应在您想共同中断的呼叫之间共享 If you attach a circuit-breaker to a particular HttpClient configuration declared via HttpClientFactory, all calls through instances of that HttpClient configuration later retrieved from HttpClientFactory by DI, will share the circuitBreaker, and thus break in common. 如果将断路器附加到通过HttpClientFactory声明的特定HttpClient配置中,则稍后通过DI从HttpClientFactory检索到的该HttpClient配置实例的所有调用将共享circuitBreaker,从而共同中断。

When using circuit-breaker with HttpClientFactory, this typically means you might declare one HttpClient configuration (typed or named) on HttpClientFactory per subsystem for which you want calls to circuit-break in common. 当将断路器与HttpClientFactory一起使用时,这通常意味着您可能希望在每个子系统上通过HttpClientFactory声明一个HttpClient配置(键入或命名),要对该子系统进行公用调用。


Sidenote: The variant of circuit-breaker selected also triggers based on consecutive fault count . 旁注:所选断路器的变型也会基于连续的故障计数触发 (Mentioned just in case an extra factor; the question posted refers to 5 errors occurring across requests but not specifically consecutively.) (提及以防万一是一个额外的因素;发布的问题是指在请求中发生的5个错误,但不是连续的。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM