192 lines
5.2 KiB
C#
192 lines
5.2 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace SharpAbp.Infrastructure.Api.Resilience
|
|
{
|
|
public enum CircuitState
|
|
{
|
|
Closed,
|
|
|
|
Open,
|
|
|
|
HalfOpen
|
|
}
|
|
|
|
public class CircuitBreakerOptions
|
|
{
|
|
public int FailureThreshold { get; set; } = 5;
|
|
|
|
public int BreakDurationSeconds { get; set; } = 30;
|
|
|
|
public int HalfOpenAttempts { get; set; } = 3;
|
|
|
|
public int SuccessThreshold { get; set; } = 2;
|
|
}
|
|
|
|
public class CircuitBreaker
|
|
{
|
|
private readonly CircuitBreakerOptions _options;
|
|
private readonly ILogger _logger;
|
|
private int _failureCount;
|
|
private int _successCount;
|
|
private int _halfOpenAttempts;
|
|
private CircuitState _state = CircuitState.Closed;
|
|
private DateTime _lastFailureTime;
|
|
private readonly object _lock = new();
|
|
|
|
public CircuitBreaker(string name, CircuitBreakerOptions options, ILogger logger)
|
|
{
|
|
Name = name;
|
|
_options = options;
|
|
_logger = logger;
|
|
}
|
|
|
|
public string Name { get; }
|
|
public CircuitState State => _state;
|
|
|
|
public async Task<T> ExecuteAsync<T>(Func<Task<T>> action)
|
|
{
|
|
if (!CanExecute())
|
|
{
|
|
throw new CircuitBreakerOpenException($"Circuit breaker '{Name}' is open");
|
|
}
|
|
|
|
try
|
|
{
|
|
var result = await action();
|
|
OnSuccess();
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
OnFailure(ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task ExecuteAsync(Func<Task> action)
|
|
{
|
|
if (!CanExecute())
|
|
{
|
|
throw new CircuitBreakerOpenException($"Circuit breaker '{Name}' is open");
|
|
}
|
|
|
|
try
|
|
{
|
|
await action();
|
|
OnSuccess();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
OnFailure(ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private bool CanExecute()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
switch (_state)
|
|
{
|
|
case CircuitState.Closed:
|
|
return true;
|
|
|
|
case CircuitState.Open:
|
|
var elapsed = (DateTime.UtcNow - _lastFailureTime).TotalSeconds;
|
|
if (elapsed >= _options.BreakDurationSeconds)
|
|
{
|
|
_state = CircuitState.HalfOpen;
|
|
_halfOpenAttempts = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case CircuitState.HalfOpen:
|
|
return _halfOpenAttempts < _options.HalfOpenAttempts;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnSuccess()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
switch (_state)
|
|
{
|
|
case CircuitState.Closed:
|
|
_failureCount = 0;
|
|
break;
|
|
|
|
case CircuitState.HalfOpen:
|
|
_successCount++;
|
|
_halfOpenAttempts++;
|
|
if (_successCount >= _options.SuccessThreshold)
|
|
{
|
|
_state = CircuitState.Closed;
|
|
_failureCount = 0;
|
|
_successCount = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnFailure(Exception ex)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_failureCount++;
|
|
_lastFailureTime = DateTime.UtcNow;
|
|
|
|
switch (_state)
|
|
{
|
|
case CircuitState.Closed:
|
|
if (_failureCount >= _options.FailureThreshold)
|
|
{
|
|
_state = CircuitState.Open;
|
|
}
|
|
break;
|
|
|
|
case CircuitState.HalfOpen:
|
|
_state = CircuitState.Open;
|
|
_halfOpenAttempts = _options.HalfOpenAttempts;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class CircuitBreakerOpenException : Exception
|
|
{
|
|
public CircuitBreakerOpenException(string message) : base(message) { }
|
|
}
|
|
|
|
public class CircuitBreakerFactory
|
|
{
|
|
private readonly ConcurrentDictionary<string, CircuitBreaker> _breakers = new();
|
|
private readonly ILoggerFactory _loggerFactory;
|
|
|
|
public CircuitBreakerFactory(ILoggerFactory loggerFactory)
|
|
{
|
|
_loggerFactory = loggerFactory;
|
|
}
|
|
|
|
public CircuitBreaker GetOrCreate(string name, CircuitBreakerOptions options = null)
|
|
{
|
|
return _breakers.GetOrAdd(name, n =>
|
|
{
|
|
var opts = options ?? new CircuitBreakerOptions();
|
|
var logger = _loggerFactory.CreateLogger<CircuitBreaker>();
|
|
return new CircuitBreaker(n, opts, logger);
|
|
});
|
|
}
|
|
}
|
|
}
|