熔断器在c#中通过状态机、异常计数和时间窗口实现故障隔离,核心是防止雪崩而非修复下游问题;polly的circuitbreakerasync在open状态下直接抛出brokencircuitexception,不发请求、不等待、不重试,30秒后自动进入halfopen并仅放行首个试探请求。

熔断器在 C# 中不是靠物理发热熔断,而是靠状态机 + 异常计数 + 时间窗口做故障隔离,核心是避免雪崩——它不解决下游服务本身的问题,只防止你反复往一个已瘫痪的接口上撞。
为什么 Polly 的 CircuitBreakerAsync 会“卡住”请求?
这不是 bug,是设计行为。当熔断器进入 Open 状态后,所有匹配该策略的请求会立即抛出 BrokenCircuitException,根本不会发出去。常见误判是以为“超时了才熔断”,其实熔断触发条件和超时无关,只看失败次数和时间窗口内是否达标。
-
CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))表示:连续 5 次失败(非超时,是HttpRequestException、5xx 等被策略捕获的异常)就跳Open,持续 30 秒 - 这 30 秒内任何调用都直接失败,不走网络,不等响应,不重试
- 30 秒后自动进入
HalfOpen,只放行第一个请求试探;若成功则恢复,若失败则重置计时器并再锁 30 秒
HandleTransientHttpError() 到底捕获哪些异常?
它不是捕获所有 HTTP 错误,而是有明确定义的子集:408(Request Timeout)、429(Too Many Requests)、5xx(除 501/505 外)、以及底层连接异常(如 HttpRequestException)。注意:400、401、403、404 这类客户端错误默认不触发熔断——它们通常代表业务逻辑问题,而非服务不可用。
- 如果想把 404 也纳入熔断(比如你依赖的某个资源端点被永久下线),得显式加
.OrResult(x => x.StatusCode == HttpStatusCode.NotFound) -
HandleResult和HandleTransientHttpError是正交的,可链式叠加,但要注意顺序:先HandleResult再HandleTransientHttpError更安全 - 不要依赖
Handle<exception></exception>捕获一切——那会把ArgumentNullException这类编程错误也当服务故障处理,反而掩盖真实问题
为什么 AddHttpClient 配置熔断后,单元测试里不生效?
因为 AddHttpClient 注册的是命名或类型化客户端,而单元测试中若直接 new MyService 或用 HttpClient 构造函数实例化,完全绕过了 DI 容器和 Polly 策略管道。熔断器只对通过 IServiceProvider.GetService<imyservice>()</imyservice> 获取的实例起作用。
- 测试时必须用
TestServer或WebApplicationFactory启动完整管线,或手动构建PolicyHttpMessageHandler并注入到HttpClient - 别在测试里 mock
IMyService接口然后验证“是否熔断”——那测的是 mock 行为,不是策略逻辑 - 真要验证熔断状态,得用
Polly.CircuitBreaker.AsyncCircuitBreakerPolicy的State属性(需反射访问内部_state字段,或改用AdvancedCircuitBreaker提供的公开状态监听)
真正容易被忽略的是半开状态(HalfOpen)下的竞争条件:多个并发请求在刚进入 HalfOpen 时,Polly 默认只允许第一个通过,其余仍被拒绝——但这不是排队机制,而是原子状态检查。如果你的下游恢复得快但不稳定,可能需要调低 HalfOpen 试探间隔,或改用 AdvancedCircuitBreaker 的自定义探测逻辑。











