直接用 Polly 而非手写断路器,因其线程安全、状态严谨、已深度集成生态;手写易致并发探测异常或内存泄漏,Polly 支持 HttpClient 原生集成及自定义策略组合与状态监听。

为什么直接用 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() .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(ex => ex.Number is 1205 or 1222) // 死锁/超时
.Or()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromMinutes(2));
调用时必须显式包裹:
try
{
await _circuitBreaker.ExecuteAsync(async () => await _repository.FetchDataAsync());
}
catch (BrokenCircuitException)
{
// 返回缓存或默认值
return _cache.GetOrAdd("fallback", _ => new List- ());
}
注意:
- 不要把
ExecuteAsync写在循环里——每次调用都计入熔断统计,高频调用会快速触发熔断 -
exceptionsAllowedBeforeBreaking是绝对次数,和时间窗口无关;适合低频、高价值操作(如支付确认) - 若需同时加重试,用
Policy.WrapAsync(retryPolicy, circuitBreaker),顺序很重要:外层是熔断器,内层是重试
自定义状态监听与诊断:别只靠日志
熔断器状态变化(如 Open → HalfOpen)是关键信号,但默认不输出。启用监听:
var breaker = Policy
.Handle()
.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 默认不解决这个问题。










