Circuit Breaker in Microservice Architecture — Resilience is the key
Microservice architecture, characterized by its distributed nature, often involves multiple services communicating over a network. While this provides flexibility and scalability, it also introduces potential points of failure. One failing service can cascade and affect other services. To mitigate this risk, the Circuit Breaker pattern is widely used. This blog will delve into the Circuit Breaker pattern, explaining its importance, functionality, and implementation in a microservices environment, particularly with C# code snippets.
Embark on a journey of continuous learning and exploration with DotNet-FullStack-Dev. Uncover more by visiting our https://dotnet-fullstack-dev.blogspot.com reach out for further information.
What is the Circuit Breaker Pattern?
The Circuit Breaker pattern is a design pattern used in microservices to detect and handle failures gracefully. It prevents a system from continuously trying to execute an operation that is likely to fail, allowing the system to recover and avoid further issues. The Circuit Breaker has three main states:
- Closed: The circuit is closed, and requests are allowed to pass through.
- Open: The circuit is open, and requests are blocked to prevent further failures.
- Half-Open: The circuit allows a limited number of requests to pass through to test if the problem still exists.
Why Use a Circuit Breaker?
- Failure Isolation: Prevents cascading failures across services.
- Improved Resilience: Allows services to fail gracefully and recover automatically.
- Better User Experience: Reduces the chance of prolonged outages and improves response times.
When to Use a Circuit Breaker?
- External Service Dependency: When your service relies on external services that might fail or become slow.
- High Failure Rate: When there is a high rate of failure in calls to a service or resource.
- Resource Protection: To protect resources from being overwhelmed by continuous retry attempts.
How the Circuit Breaker Works
- Normal Operation (Closed State): Requests pass through normally until a certain threshold of failures is reached.
- Failure Detected (Open State): Once the failure threshold is met, the circuit opens, and requests are blocked or redirected.
- Recovery Check (Half-Open State): After a predefined timeout, the circuit enters a half-open state, allowing a few test requests.
- Recovery or Further Failure: If test requests succeed, the circuit closes. If they fail, the circuit opens again.
Implementing Circuit Breaker in .NET with Polly
Polly is a .NET library that provides resilience and transient-fault-handling capabilities, such as retries, circuit breakers, timeouts, and bulkhead isolation. Below is an implementation of the Circuit Breaker pattern using Polly.
Install-Package Polly:
Install-Package Polly
Create a Simple Service Interface:
public interface IExternalService
{
Task<string> GetDataAsync();
}
Implement the Service:
public class ExternalService : IExternalService
{
private readonly HttpClient _httpClient;
public ExternalService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetDataAsync()
{
var response = await _httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
Configure Polly Circuit Breaker Policy:
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromMinutes(1),
onBreak: (exception, timespan) =>
{
Console.WriteLine($"Circuit broken due to: {exception.Message}");
},
onReset: () => Console.WriteLine("Circuit closed."),
onHalfOpen: () => Console.WriteLine("Circuit in half-open state.")
);
Integrate the Policy in Your Service Call:
public class ResilientService : IExternalService
{
private readonly IExternalService _externalService;
private readonly AsyncPolicy _policy;
public ResilientService(IExternalService externalService, AsyncPolicy policy)
{
_externalService = externalService;
_policy = policy;
}
public async Task<string> GetDataAsync()
{
return await _policy.ExecuteAsync(() => _externalService.GetDataAsync());
}
}
Register Services in Dependency Injection:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<IExternalService, ExternalService>();
services.AddSingleton<IAsyncPolicy>(circuitBreakerPolicy);
services.AddTransient<IExternalService, ResilientService>();
}
Complete Example
Program.cs:
public class Program
{
public static async Task Main(string[] args)
{
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IExternalService>();
try
{
var data = await service.GetDataAsync();
Console.WriteLine(data);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<IExternalService, ExternalService>();
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromMinutes(1),
onBreak: (exception, timespan) =>
{
Console.WriteLine($"Circuit broken due to: {exception.Message}");
},
onReset: () => Console.WriteLine("Circuit closed."),
onHalfOpen: () => Console.WriteLine("Circuit in half-open state.")
);
services.AddSingleton<IAsyncPolicy>(circuitBreakerPolicy);
services.AddTransient<IExternalService, ResilientService>();
}
}
ExternalService.cs:
public class ExternalService : IExternalService
{
private readonly HttpClient _httpClient;
public ExternalService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetDataAsync()
{
var response = await _httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
ResilientService.cs:
public class ResilientService : IExternalService
{
private readonly IExternalService _externalService;
private readonly AsyncPolicy _policy;
public ResilientService(IExternalService externalService, AsyncPolicy policy)
{
_externalService = externalService;
_policy = policy;
}
public async Task<string> GetDataAsync()
{
return await _policy.ExecuteAsync(() => _externalService.GetDataAsync());
}
}
Conclusion
The Circuit Breaker pattern is essential for building resilient microservices. It helps prevent cascading failures and allows the system to recover gracefully. By using Polly in .NET, you can easily implement this pattern and enhance the robustness of your applications. Understanding when and how to use different states of the Circuit Breaker can significantly improve the reliability and user experience of your microservices architecture.
You may also like: https://medium.com/@siva.veeravarapu/api-gateway-in-net-microservice-architecture-411cdf52c22d