What is the Circuit Breaker pattern?
4 minadvancedmicroservicesresiliencecircuit-breakerPolly
Quick Answer
The Circuit Breaker pattern stops an application from repeatedly calling a failing dependency. It tracks failures and, once a threshold is crossed, 'opens' to fail fast (avoiding wasted calls and cascading failures); after a timeout it goes 'half-open' to test recovery, then 'closes' on success. In .NET it's commonly implemented with Polly. It improves resilience and gives the failing service time to recover.
Detailed Answer
The Circuit Breaker pattern prevents an application from repeatedly trying to execute an operation that's likely to fail, allowing it to continue without waiting for the fault to be fixed or wasting CPU cycles.
States:
- Closed: Requests flow normally, failures are counted
- Open: Requests fail immediately without attempting the call
- Half-Open: Limited requests are allowed to test if the issue is resolved
Implementation with Polly:
// Install: Install-Package Polly
// Install: Install-Package Microsoft.Extensions.Http.Polly
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("OrderService", c =>
{
c.BaseAddress = new Uri("http://order-service:5000");
})
.AddPolicyHandler(GetCircuitBreakerPolicy())
.AddPolicyHandler(GetRetryPolicy());
}
private IAsyncPolicy GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (result, timespan) =>
{
// Log circuit breaker opened
Console.WriteLine($"Circuit breaker opened for {timespan.TotalSeconds}s");
},
onReset: () =>
{
// Log circuit breaker reset
Console.WriteLine("Circuit breaker reset");
},
onHalfOpen: () =>
{
// Log circuit breaker half-open
Console.WriteLine("Circuit breaker half-open");
});
}
private IAsyncPolicy GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} after {timespan.TotalSeconds}s");
});
}
Custom Circuit Breaker:
public class CircuitBreaker
{
private readonly int _threshold;
private readonly TimeSpan _timeout;
private int _failureCount;
private DateTime _lastFailureTime;
private CircuitBreakerState _state = CircuitBreakerState.Closed;
public async Task ExecuteAsync(Func<Task> operation)
{
if (_state == CircuitBreakerState.Open)
{
if (DateTime.UtcNow - _lastFailureTime > _timeout)
{
_state = CircuitBreakerState.HalfOpen;
}
else
{
throw new CircuitBreakerOpenException();
}
}
try
{
var result = await operation();
if (_state == CircuitBreakerState.HalfOpen)
{
_state = CircuitBreakerState.Closed;
_failureCount = 0;
}
return result;
}
catch (Exception)
{
_failureCount++;
_lastFailureTime = DateTime.UtcNow;
if (_failureCount >= _threshold)
{
_state = CircuitBreakerState.Open;
}
throw;
}
}
}
public enum CircuitBreakerState
{
Closed,
Open,
HalfOpen
}