Files
BuYuHouDuan/Infrastructure/SharpAbp.Infrastructure.Api/Resilience/CircuitBreaker.cs
T
2026-10-12 16:22:04 +08:00

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);
});
}
}
}