为什么直接用 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默认不解决这个问题。
