熔断是故障响应机制,限流是资源控制机制,二者目标不同、不可互换;必须用policywrapasync组合且顺序为限流→熔断→重试,避免策略失效。

直接用 Polly 的 CircuitBreakerPolicy,别手写;限流必须搭配 BulkheadPolicy 或 RateLimitPolicy(.NET 8+),两者目标不同、不能互相替代。
为什么熔断和限流不能混为一谈
熔断是「故障响应机制」:当下游持续失败(如连续 5 次 HttpRequestException),自动跳闸进入 Open 状态,后续请求直接抛 BrokenCircuitException,不发出去。限流是「资源控制机制」:限制并发请求数或单位时间调用量,防止单个服务/接口被突发流量打垮,和失败率无关。
常见错误现象:CircuitBreakerPolicy 配了但压测时仍大量请求涌向下游;或用了 WaitAndRetryAsync 却没设 BreakDuration,导致熔断形同虚设。
- 熔断触发条件只认异常或自定义
HandleResult判断逻辑,HTTP 200 + 业务错误码(如{"code":500})默认不触发 - 限流策略若用
BulkheadPolicy,需注意maxParallelization和maxQueuingActions两个参数——前者卡并发数,后者卡排队数,超队列直接丢弃(抛BulkheadRejectedException) - .NET 8 起推荐用原生
RateLimiting(AddRateLimiter),它支持滑动窗口、令牌桶、并发限制等多种算法,且与IHttpClientFactory深度集成
HttpClient 场景下怎么同时加熔断 + 限流
必须用 PolicyWrapAsync 组合,顺序不能错:外层限流(防洪峰)、中层熔断(防雪崩)、内层重试(治抖动)。如果把熔断放最外层,限流就失效了——因为熔断后请求根本进不到限流环节。
示例(.NET 6+,使用 Microsoft.Extensions.Http.Polly):
services.AddHttpClient<iorderservice>("order-api")
.AddPolicyHandler(Policy.WrapAsync(
Policy.BulkheadAsync<httpresponsemessage>(
maxParallelization: 10,
maxQueuingActions: 5),
Policy.Handle<httprequestexception>()
.OrResult<httpresponsemessage>(r => r.StatusCode == HttpStatusCode.ServiceUnavailable)
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromMinutes(1)),
Policy.Handle<httprequestexception>()
.WaitAndRetryAsync(
retryCount: 2,
sleepDurationProvider: _ => TimeSpan.FromMilliseconds(200))));
</httprequestexception></httpresponsemessage></httprequestexception></httpresponsemessage></iorderservice>
-
BulkheadPolicy是硬性并发控制,适合保护下游连接池或数据库线程数;RateLimitPolicy(.NET 8+)更适合按 QPS 限流,比如每秒最多 100 次调用 - 不要给
HttpClient.Timeout设具体值(如TimeSpan.FromSeconds(30)),否则会和 Polly 的重试/超时策略冲突;应设为TimeSpan.MaxValue,把超时控制权完全交给策略 - 所有策略实例必须注册为单例(
AddSingleton)或通过AddPolicyHandler注入,避免每次请求 new 新实例——状态不共享,熔断器永远无法积累失败计数
如何让熔断器识别业务级失败(非 HTTP 状态码)
默认 Handle<httprequestexception>()</httprequestexception> 只捕获网络异常,4xx/5xx 响应体里带业务错误码(如 {"code": -1, "msg": "库存不足"})不会触发熔断。必须用 OrResult + 手动解析响应内容。
关键点:响应体只能读一次,所以得用 HttpContent.ReadAsStringAsync() 并缓存结果,否则后续 ReadAsStringAsync() 返回空字符串。
- 在
OrResult里检查r.IsSuccessStatusCode == false是基础,但不够;还得解析 JSON 判断content.code != 0或content.status == "ERROR" - 推荐封装一个扩展方法
IsBusinessError(this HttpResponseMessage response),内部用response.Content.ReadAsStreamAsync()避免字符串拷贝,再用JsonDocument.ParseStream快速提取字段 - 别在
OnBreak回调里做耗时操作(如发告警邮件),它运行在策略线程上,阻塞会导致状态机卡死;应投递到后台队列或用Task.Run脱离上下文
最容易被忽略的线程安全细节
CircuitBreakerPolicy 内部有状态机(Closed/Open/HalfOpen)和滑动窗口统计,所有操作都要求线程安全。但开发者常在三个地方踩坑:
- 多个命名 HttpClient 共享同一个策略实例:可以,Polly 是线程安全的;但若手动 new 多个策略实例去分别 use,则每个实例独立计数,熔断无效
-
HalfOpen状态下,默认只允许 1 个试探请求,其余立即失败——这是防止试探洪峰的关键,别用AllowHalfOpenAfter改成大于 1 的值 - 自定义
onBreak/onReset回调里访问HttpContext或IServiceScope:这些对象不是线程安全的,可能引发ObjectDisposedException,应回调中只记录日志或发信号量
真正难的不是配参数,而是理解每个策略的生效边界和协作顺序——熔断不管流量大小,限流不管是否出错,它们像两道独立的闸门,必须装对位置、拧紧螺栓,否则洪水来了,谁也拦不住。











