Flurl uses Polly to add a retry mechanism

When using Flurl as an HttpClient to request from the server, the request may fail due to network or other reasons, such as HttpStatusCode.NotFound HttpStatusCode.ServiceUnavailable , HttpStatusCode. RequestTimeout, etc; There are many HttpClientFactories on the network that use Polly to implement retry content. However, it is inconvenient for those who are used to using Flurl to switch back to IHttpClient, because this article uses Flurl to implement retry mechanism to sort out;

Do not use Polly to test

  1. Provide an interface for request testing

     [Route("api/[controller]")] [ApiController] public class PollyController : ControllerBase { private readonly ILogger<PollyController> _logger; public PollyController(ILogger<PollyController> logger) { _logger = logger; } // GET: api/<PollyController> [HttpGet] public IActionResult Get() { var random = new Random(). Next(0, 8); switch (random) { case 0: _logger. LogInformation("About to serve a 404"); return StatusCode(StatusCodes. Status404NotFound); case 1: _logger. LogInformation("About to serve a 503"); return StatusCode(StatusCodes. Status503ServiceUnavailable); case 2: _logger. LogInformation("Sleeping for 10 seconds then serving a 504"); Thread.Sleep(10000); _logger. LogInformation("About to serve a 504"); return StatusCode(StatusCodes. Status504GatewayTimeout); default: _logger. LogInformation("About to correctly serve a 200 response"); return Ok(new {time = DateTime.Now.ToLocalTime()}); } } }
  2. Create a request client

     public class HomeController : Controller { private readonly ILogger<HomeController> _logger; public HomeController(ILogger<HomeController> logger) { _logger = logger; } public async Task<IActionResult> Index() { try { var time = await " http://127.0.0.1:5000/api/polly " .GetJsonAsync(); _logger. LogInformation($"App: success - {time.time}"); return View(time.time); } catch (Exception e) { _logger. LogWarning($"App: failed - {e.Message}"); throw; } } }
  3. Try the request, and you can find many places where the request fails. This situation is not ideal. The server has a high probability of not responding normally

     info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to serve a 404 info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to correctly serve a 200 response info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to correctly serve a 200 response info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to correctly serve a 200 response info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to serve a 503 info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to serve a 503 info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to correctly serve a 200 response info: SuppertRcsInterfaceTest.Controllers.PollyController[0] About to serve a 404

    Is there any solution to this problem? The answer is yes. The rude idea is to make a request again after failure. It would be troublesome and inconvenient for unified management to directly process the logic in the return result of Flurl, so Polly was found

Use Polly to test

  1. First install Polly, Install-Package Polly

  2. The following is a brief introduction of Polly, followed by Policy Code snippet for

    Polly's seven strategies: retry, open circuit, timeout, isolation, rollback, and cache. This article uses retry and timeout strategies

    Retry: Automatic retry in case of failure. This is a common scenario

    Circuit breaker: When the system encounters a serious problem, it is better to quickly feed back the failure than to let the user/caller wait. It limits the consumption of system errors and helps the system recover. For example, when we call a third-party API, the API does not respond for a long time, and the other server may be paralyzed. If our system keeps retrying, Unfair will increase the burden of the system, and may also affect other tasks of the system. Therefore, when the number of system errors exceeds the specified threshold, the current thread is interrupted and continues after a period of time; For example: Policy.Handle<SomeException>(). CircuitBreaker(2, TimeSpan.FromMinutes(1)); It means that the system stops when an exception occurs twice and waits for 1 minute before continuing. You can also define the interrupted callback and the restarted callback when the circuit is broken

    Timeout: when the system has timed out waiting for a certain time, it can be judged that there is no successful result when it is in place; For example, in normal times, a network request is completed in an instant. If a network request has not been completed for more than 30 seconds, we can determine that it is impossible to return a successful result. Therefore, we need to set the timeout of the system to prevent the system from waiting unnecessarily for a long time; For example: Policy.Timeout(30, (context, span, task) => {// do something}); It indicates that the timeout cannot exceed 30 seconds. Otherwise, it will be considered as an error and a callback will be executed

    Bulkhead Isolation: When a failure occurs in one part of the system, multiple failed calls may be triggered, which consumes a lot of resources. The failure of the downstream system may lead to the call of the upstream failure, or even spread to the system crash. Therefore, controllable operations should be limited to a fixed size resource pool, To isolate operations that may potentially affect each other; For example: Policy.Bulkhead(12, context => {// do something}); Indicates that at most 12 threads are allowed to execute concurrently. If the execution is rejected, the callback will be executed

    Fallback: If some errors cannot be avoided, there must be an alternative solution. When an unavoidable error occurs, we must have a reasonable return to replace the failure; For example: Policy.Handle<Whatever>(). Fallback<UserAvatar>(() => UserAvatar. GetRandomAvatar()); It means that when the user does not upload his/her avatar, we will give him/her a default avatar

    Cache: Generally, we cache frequently used resources that do not change much to improve the response speed of the system. If we do not encapsulate calls to cache resources, we should first determine whether there is such a resource in the cache when we call, and then return from the cache if there is one, otherwise we will get it from the resource storage and cache it before returning, In addition, sometimes the cache expiration and how to update the cache should be considered; Polly provides cache policy support, which makes the problem simple

    Policy Wrap: An operation can have many different faults, and different fault handling requires different policies. These different policies must be packaged together. As a policy package, they can be applied to the same operation. This is Polly's elastic property, that is, different policies can be flexibly combined

    more...

     using System; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Flurl.Http.Configuration; using Microsoft.Extensions.Logging; using Polly; using Polly.Retry; using Polly.Timeout; using Polly.Wrap; namespace WithPollyClient.Services { public class Policies { private readonly ILogger<Policies> _logger; public Policies(ILogger<Policies> logger) { _logger = logger; } private AsyncTimeoutPolicy<HttpResponseMessage> TimeoutPolicy { get { return Policy.TimeoutAsync<HttpResponseMessage>(3, (context, span, task) => { _logger. LogInformation($"Policy: Timeout delegate fired after {span.Seconds} seconds"); return Task.CompletedTask; }); } } private AsyncRetryPolicy<HttpResponseMessage> RetryPolicy { get { HttpStatusCode[] retryStatus = { HttpStatusCode.NotFound, HttpStatusCode.ServiceUnavailable, HttpStatusCode.RequestTimeout }; return Policy .HandleResult<HttpResponseMessage>(r => retryStatus.Contains(r.StatusCode)) .Or<TimeoutRejectedException>() .WaitAndRetryAsync(new[] { //It means three retries, one second after the first, two seconds after the second, and four seconds after the third TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4) }, (result,  span, count, context) => { _logger. LogInformation($"Policy: Retry delegate fired, attempt {count}"); }); } } public AsyncPolicyWrap<HttpResponseMessage> PolicyStrategy => Policy.WrapAsync(RetryPolicy, TimeoutPolicy); } public class PolicyHandler : DelegatingHandler { private readonly Policies _policies; public PolicyHandler(Policies policies) { _policies = policies; } protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,  CancellationToken cancellationToken) { return _policies.PolicyStrategy.ExecuteAsync(ct => base. SendAsync(request,  ct), cancellationToken); } } public class PollyHttpClientFactory : DefaultHttpClientFactory { private readonly Policies _policies; public PollyHttpClientFactory(Policies policies) { _policies = policies; } public override HttpMessageHandler CreateMessageHandler() { return new PolicyHandler(_policies) { InnerHandler = base.CreateMessageHandler() }; } } }
  3. Next on Starup Configure Flurl in

     public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddSingleton<Policies>(); } // This method gets called by the runtime.  Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app,  IWebHostEnvironment env) { var policies = app.ApplicationServices.GetService<Policies>(); FlurlHttp.Configure(setting => setting.HttpClientFactory = new PollyHttpClientFactory(policies)); ......
  4. Try the request again, and you can see that the result is very ideal

     WithPollyClient.Services.Policies: Information: Policy: Retry delegate fired, attempt 1 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:14 WithPollyClient.Services.Policies: Information: Policy: Retry delegate fired, attempt 1 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:17 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:22 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:23 WithPollyClient.Services.Policies: Information: Policy: Retry delegate fired, attempt 1 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:25 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:31 WithPollyClient.Services.Policies: Information: Policy: Retry delegate fired, attempt 1 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:34 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:39 WithPollyClient.Services.Policies: Information: Policy: Retry delegate fired, attempt 1 WithPollyClient.Services.Policies: Information: Policy: Timeout delegate fired after 3 seconds WithPollyClient.Services.Policies: Information: Policy: Retry delegate fired, attempt 2 WithPollyClient.Controllers.HomeController: Information: App: success - 2021/3/14 16:50:46

Usage in rich client

Sometimes, for example, in WPF Or other rich clients often use Flurl, as follows

 var time = await Policy .Handle<FlurlHttpException>() .OrResult<IFlurlResponse>(r => ! r.ResponseMessage.IsSuccessStatusCode) .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4) }, (result,  span, count, context) => { _logger. LogInformation(count. ToString()); }) .ExecuteAsync(() => " http://127.0.0.1:5000/api/polly ".WithTimeout(3).GetAsync()) .ReceiveJson(); _logger. LogInformation($"App: success - {time.time}"); return View(time.time);
posted @ 2021-03-14 17:08   illegal keyword   Reading( five hundred and sixteen Comments( one edit   Collection   report