Skip to main content

Polly retry pattern in .Net Core

 

The Polly retry pattern is a powerful mechanism for handling transient faults in applications by retrying failed operations. Polly is a .NET library that provides resilience strategies like retries, circuit breakers, timeouts, and fallback mechanisms. The retry pattern is particularly useful when dealing with unreliable external services, such as APIs or databases, that may occasionally fail but succeed on subsequent attempts.

In this blog, we will explore how to implement the Polly retry pattern in .NET Core, particularly in the context of using HttpClient with IHttpClientFactory. We'll also see how to configure retry policies to make your HTTP requests more resilient.

Why Use the Retry Pattern?

  1. Transient Failures: Network failures or intermittent connectivity issues may cause operations to fail temporarily.
  2. Rate Limiting: External services may apply rate limits and reject requests, but retrying after a delay can succeed.
  3. Load Balancing: Sometimes, servers are temporarily overloaded, and retrying the request may succeed after a few seconds.
  4. Failover: When working with multiple instances of a service, one instance might fail, but a retry could succeed on another instance.

Installing Polly

Polly is available as a NuGet package. You can add it to your project using the following command:

dotnet add package Polly

Integrating Polly with HttpClient and IHttpClientFactory

Polly can be easily integrated with IHttpClientFactory to apply retry policies to HTTP requests. Here’s how you can set up a retry policy for an HttpClient that makes requests to an external API.

Step 1: Register IHttpClientFactory and Polly Policies

In your Startup.cs or Program.cs (for .NET 6+), you will register your HttpClient with Polly retry policies.

using Polly;
using Polly.Extensions.Http;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Register IHttpClientFactory with a retry policy
        services.AddHttpClient("RetryClient", client =>
        {
            client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
            client.DefaultRequestHeaders.Add("Accept", "application/json");
        })
        .AddPolicyHandler(GetRetryPolicy()); // Add retry policy

        services.AddControllers();
    }

    // Define the retry policy
    private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
    {
        return HttpPolicyExtensions
            .HandleTransientHttpError()  // Handles 5xx, 408, and network failures
            .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.TooManyRequests) // Custom condition
            .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // Exponential backoff
    }
}

Explanation of the Retry Policy:

  1. HandleTransientHttpError(): This handles HTTP 5xx status codes (server errors), HTTP 408 (request timeout), and other network issues.
  2. OrResult(): This allows you to handle custom conditions like 429 Too Many Requests, which is a common status for rate limiting.
  3. WaitAndRetryAsync(): This specifies how many retries should be attempted (3 retries in this case) and the delay between retries. Here, we use exponential backoff (2^retryAttempt), meaning the delays will be 2, 4, and 8 seconds.

Step 2: Using the HttpClient with Retry Policy

In your controller or service, you can inject the HttpClientFactory to use the HttpClient configured with the retry policy.

using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class ItemsController : ControllerBase
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ItemsController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet("retry-example")]
    public async Task<IActionResult> GetDataWithRetry()
    {
        // Create an HttpClient with the retry policy
        var client = _httpClientFactory.CreateClient("RetryClient");

        // Make an HTTP request to an external API
        var response = await client.GetAsync("/posts");

        if (response.IsSuccessStatusCode)
        {
            var data = await response.Content.ReadAsStringAsync();
            return Ok(data);
        }

        return StatusCode((int)response.StatusCode, "Failed to retrieve data after retries");
    }
}

Step 3: Customizing the Retry Policy

You can customize the retry policy by adding conditions based on the specific needs of your application. For instance, you can add more specific error conditions or change the backoff strategy.

Retry Based on Specific HTTP Status Codes:

If you want to retry only for certain status codes, like 500 Internal Server Error or 503 Service Unavailable, you can customize the policy as follows:

private static IAsyncPolicy<HttpResponseMessage> GetCustomRetryPolicy()
{
    return Policy
        .HandleResult<HttpResponseMessage>(r => r.StatusCode == System.Net.HttpStatusCode.InternalServerError || 
                                                r.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(2));
}

Jitter Strategy:

A jitter strategy adds randomness to retry delays, which helps avoid thundering herd problems, where many requests retry at the same time and overload the system.

private static IAsyncPolicy<HttpResponseMessage> GetJitterRetryPolicy()
{
    var jitterer = new Random();
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) 
                                              + TimeSpan.FromMilliseconds(jitterer.Next(0, 1000)));
}

Retry on HTTP Method:

You can also configure the retry policy to apply only to certain HTTP methods, such as GET, to ensure retry is not applied to non-idempotent operations like POST.

private static IAsyncPolicy<HttpResponseMessage> GetMethodSpecificRetryPolicy()
{
    return Policy
        .Handle<HttpRequestException>()
        .OrResult<HttpResponseMessage>(r => r.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(2), onRetry: (outcome, timespan, retryCount, context) =>
        {
            if (context["httpMethod"] as string == HttpMethod.Get.Method)
            {
                Console.WriteLine($"Retrying GET request: {retryCount}");
            }
        });
}
Then, use context when calling the HTTP client:
var client = _httpClientFactory.CreateClient("RetryClient");
var context = new Context();
context["httpMethod"] = HttpMethod.Get.Method;
var response = await Policy.ExecuteAsync(ctx => client.GetAsync("/posts"), context);

Advanced Polly Features

  1. Circuit Breaker: You can combine retry with the circuit breaker pattern, which stops retrying after a certain number of failures to prevent overwhelming the service.

  2. Bulkhead Isolation: Bulkhead limits the number of concurrent requests to a resource, ensuring that failures in one part of the system don’t overload the rest.

  3. Timeouts: Use Polly's timeout policy to prevent long-running requests from hanging indefinitely.

Conclusion

The Polly retry pattern, combined with IHttpClientFactory, provides a robust and flexible way to handle transient faults in your .NET Core applications. By implementing retry policies, you can make your HTTP communication more resilient to intermittent network failures, rate limiting, and other external service issues.

Incorporating these resilience strategies, like retry with exponential backoff or jitter, ensures that your application can recover from failures without overloading external services. You can further customize Polly by integrating features like circuit breakers, bulkhead isolation, and timeouts to build a resilient and fault-tolerant microservice architecture.

With Polly, your .NET Core applications become much more reliable when dealing with flaky or unreliable external services.

Comments

Popular posts from this blog

C# : How can we access private method outside class

Introduction In object-oriented programming, encapsulation is a fundamental principle that restricts direct access to the internal implementation details of a class. Private methods, being part of this internal implementation, are designed to be accessible only within the confines of the class they belong to. However, there might be scenarios where you need to access a private method from outside the class. In this blog post, we'll explore several techniques to achieve this in C#. 1. Reflection: A Powerful Yet Delicate Approach Reflection is a mechanism in C# that allows inspecting and interacting with metadata about types, fields, properties, and methods. While it provides a way to access private methods, it should be used cautiously due to its potential impact on maintainability and performance. using System ; using System . Reflection ; public class MyClass { private void PrivateMethod ( ) { Console . WriteLine ( "This is a private method."...

20+ LINQ Concepts with .Net Code

LINQ   (Language Integrated Query) is one of the most powerful features in .NET, providing a unified syntax to query collections, databases, XML, and other data sources. Below are 20+ important LINQ concepts, their explanations, and code snippets to help you understand their usage. 1.  Where  (Filtering) The  Where()  method is used to filter a collection based on a given condition. var numbers = new List < int > { 1 , 2 , 3 , 4 , 5 , 6 } ; var evenNumbers = numbers . Where ( n => n % 2 == 0 ) . ToList ( ) ; // Output: [2, 4, 6] C# Copy 2.  Select  (Projection) The  Select()  method projects each element of a sequence into a new form, allowing transformation of data. var employees = new List < Employee > { /* ... */ } ; var employeeNames = employees . Select ( e => e . Name ) . ToList ( ) ; // Output: List of employee names C# Copy 3.  OrderBy  (Sorting in Ascending Order) The  Or...

C# : Understanding Types of Classes

In C#, classes serve as the building blocks of object-oriented programming, providing a blueprint for creating objects. Understanding the types of classes and their applications is crucial for designing robust and maintainable software. In this blog, we’ll delve into various types of classes in C#, accompanied by real-world scenarios and code snippets for a practical understanding. 1. Regular (Instance) Classes Definition: Regular classes are the most common type and are used to create instances or objects. They can contain fields, properties, methods, and other members. Example Scenario: A Person class representing individual persons with properties like Name and Age. public class Person { public string Name { get ; set ; } public int Age { get ; set ; } } 2. Static Classes Definition: A static class cannot be instantiated and can only contain static members (methods, properties, fields). It’s often used for utility functions. Example Scenario: A MathUtility cla...