c# 如何在c#中实现断路器(Circuit Breaker)模式

来源:这里教程网 时间:2026-02-21 17:36:34 作者:

为什么直接用
Polly
而不是手写断路器

手写一个线程安全、状态切换严谨、支持超时/重试/降级的断路器非常容易出错。比如

CircuitState.HalfOpen
下并发请求可能触发多次探测,或计时器未正确清理导致内存泄漏。生产环境建议直接用成熟库——
Polly
是 .NET 生态事实标准,已深度适配
IHttpClientFactory
Microsoft.Extensions.DependencyInjection

安装与基础用法:
Polly
+
AddCircuitBreaker

从 .NET 6 开始,

Microsoft.Extensions.Http.Resilience
提供了原生集成(基于 Polly),推荐优先使用:

dotnet add package Microsoft.Extensions.Http.Resilience

Program.cs
中注册:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<MyApiClient>()
    .AddCircuitBreaker(policy => policy
        .HandledStatusCodes(500, 502, 503, 504)
        .FailureThreshold(0.3) // 连续失败率阈值
        .SamplingDuration(TimeSpan.FromSeconds(30))
        .MinimumThroughput(10) // 每 30 秒至少 10 次调用才触发统计
        .BreakDuration(TimeSpan.FromMinutes(1))); // 熔断时长

关键点:

FailureThreshold
不是固定次数,而是失败率;低流量下即使只失败 1 次也可能不触发熔断(因未达
MinimumThroughput
BreakDuration
是“硬暂停”,期间所有请求直接抛
BrokenCircuitException
,不会转发
该配置仅对
HttpClient
生效;若需保护普通方法(如数据库访问),仍需手动包装
Policy

保护非 HTTP 方法:用
Policy.WrapAsync
组合策略

比如封装一个可能抛异常的仓储方法:

private readonly AsyncCircuitBreakerPolicy _circuitBreaker = Policy
    .Handle<SqlException>(ex => ex.Number is 1205 or 1222) // 死锁/超时
    .Or<TimeoutException>()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 3,
        durationOfBreak: TimeSpan.FromMinutes(2));

调用时必须显式包裹:

try
{
    await _circuitBreaker.ExecuteAsync(async () => await _repository.FetchDataAsync());
}
catch (BrokenCircuitException)
{
    // 返回缓存或默认值
    return _cache.GetOrAdd("fallback", _ => new List<Item>());
}

注意:

不要把
ExecuteAsync
写在循环里——每次调用都计入熔断统计,高频调用会快速触发熔断
exceptionsAllowedBeforeBreaking
是绝对次数,和时间窗口无关;适合低频、高价值操作(如支付确认)
若需同时加重试,用
Policy.WrapAsync(retryPolicy, circuitBreaker)
,顺序很重要:外层是熔断器,内层是重试

自定义状态监听与诊断:别只靠日志

熔断器状态变化(如

Open → HalfOpen
)是关键信号,但默认不输出。启用监听:

var breaker = Policy
    .Handle<Exception>()
    .CircuitBreakerAsync(3, TimeSpan.FromMinutes(1),
        onBreak: (ex, ts) => {
            Console.WriteLine($"Circuit broken for {ts.TotalMinutes} min due to {ex.GetType().Name}");
        },
        onReset: () => Console.WriteLine("Circuit reset"),
        onHalfOpen: () => Console.WriteLine("Circuit half-open: allowing one probe")); 

生产环境应将这些回调对接到指标系统(如 Prometheus 的

counter
),而不是仅打印日志。特别注意
onHalfOpen
——它只在第一个试探请求前触发,不代表试探成功。

真正难的是状态同步:分布式场景下,单机熔断器无法共享状态。这时得上

Redis
或专用服务(如 Resilience4j 的集中式断路器),
Polly
默认不解决这个问题。

相关推荐

热文推荐