应直接使用Polly而非手写断路器,因其内置状态机、滑动窗口、线程安全降级等生产级能力,可避免并发竞争、超时优先级错乱、半开洪峰等雪崩风险。

为什么直接用 Polly 而不是自己手写断路器
自己实现断路器看似可控,实际会漏掉大量边界情况:比如并发请求下状态竞争、超时与异常的优先级判断、降级逻辑的线程安全、恢复半开状态的定时器精度等。Polly 是经过生产验证的弹性库,CircuitBreakerPolicy 内置了状态机(Closed/Open/HalfOpen)、滑动窗口统计、自动重试退避、同步/异步统一接口——这些不是“锦上添花”,而是避免线上雪崩的刚性需求。
常见错误现象:OperationCanceledException 频发但没触发熔断;降级返回值被缓存导致后续请求永远走降级;半开状态下多个请求同时试探下游,压垮已脆弱的服务。
- 必须用
AddPolicy或PolicyWrap注入到HttpClient的生命周期里,不能每次请求 new 一个策略实例 -
HandleResult和Handle要分开配置:HTTP 500 是异常,404 是正常响应但可能需降级,得靠HandleResult捕获 - 半开状态默认只允许 1 个请求试探,其余立即失败——这是防止试探洪峰的关键,别用
AllowHalfOpenAfter随意放大
HttpClient + Polly 组合的最小可靠写法
别在业务代码里手动调用 ExecuteAsync,那等于放弃 IHttpClientFactory 的连接池和策略复用能力。正确姿势是注册命名客户端时绑定策略。
使用场景:ASP.NET Core 6+,需要对特定下游服务(如订单服务)做熔断+重试+降级。
services.AddHttpClient<iorderservice orderservice>("order-api")
.AddPolicyHandler(Policy
.Handle<httprequestexception>()
.OrResult<httpresponsemessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: (retryAttempt) => TimeSpan.FromMilliseconds(Math.Pow(2, retryAttempt) * 100)))
.AddPolicyHandler(Policy
.Handle<httprequestexception>()
.OrResult<httpresponsemessage>(r => r.StatusCode == HttpStatusCode.ServiceUnavailable)
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromMinutes(1)))
.AddPolicyHandler((_, _) => Policy<httpresponsemessage>.Handle<exception>().FallbackAsync(_ => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"data\":null}", Encoding.UTF8, "application/json") })));</exception></httpresponsemessage></httpresponsemessage></httprequestexception></httpresponsemessage></httprequestexception></iorderservice>
参数差异:WaitAndRetryAsync 的 sleepDurationProvider 必须返回 TimeSpan,不能直接写 TimeSpan.FromSeconds(1)——否则所有重试间隔固定,失去指数退避意义。
降级逻辑里最容易被忽略的三件事
降级不是“随便返回个默认值”,它直接影响前端行为和用户感知。很多团队把降级写成硬编码字符串,结果上游 JSON 解析失败,反而引发新异常。
- 降级响应必须和原始接口保持相同
Content-Type和结构,比如原接口返回application/json,降级也得用StringContent包装合法 JSON,不能只写"{}" - 不要在降级里访问外部资源(如本地缓存、DB),否则降级本身变成故障点;如果真要查缓存,确保用
MemoryCache这类无 IO 的内存方案 -
FallbackAsync的委托参数是Context,里面含Exception和Result,务必检查Context.Exception类型再决定是否记录日志——否则熔断期间日志刷屏
性能和兼容性踩坑点
Polly 本身轻量,但策略组合不当会显著拖慢吞吐。尤其在高并发下,PolicyWrap 的嵌套层数和异常捕获范围直接影响 CPU 占用。
性能影响:TimeoutPolicy 必须设为 TimeoutStrategy.Pessimistic(默认),否则乐观模式会在 Task 取消后继续执行,浪费线程资源;CircuitBreaker 的 SamplingDuration 默认 60 秒,短周期统计(如 10 秒)会让熔断更敏感,但也增加计数器开销。
兼容性影响:.NET 5+ 才支持 ValueTask 版本的策略;若项目还在 .NET Core 3.1,别用 AsyncPolicy 的泛型重载,老版本只认 Task。
容易踩的坑:AddPolicyHandler 注册顺序决定执行顺序——重试要在熔断外层,否则熔断打开后重试就失效了;反过来,降级必须在最外层,否则内层策略抛出的异常可能绕过降级。
复杂点在于状态共享:同一个 HttpClient 实例被多个请求复用,而 CircuitBreaker 状态是全局的。这意味着一个下游服务出问题,所有调它的请求都会被熔断——这本来就是设计目标,但得确认你真想要这种强耦合。










