Handle捕获不到503因HttpClient不抛异常而返回HttpResponseMessage,须用OrResult或HttpPolicyExtensions.HandleTransientHttpError;WaitAndRetryAsync禁用手写Math.Pow以防重试风暴,应使用DecorrelatedJitterBackoffV2;AddPolicyHandler必须绑定命名HttpClient并经IHttpClientFactory注入才生效。

Handle<httprequestexception>()</httprequestexception> 捕不到 503,WaitAndRetryAsync 手写 Math.Pow 会引发重试风暴,AddPolicyHandler 不注册到 IHttpClientFactory 就等于没配——Polly 重试策略不是“加了就生效”,它必须精准捕获异常、带 jitter 退避、嵌套顺序正确,三者缺一不可。
为什么 Handle 完全捕获不到 503?
HTTP 请求返回 HttpStatusCode.ServiceUnavailable(503)时,HttpClient 不抛异常,而是正常返回 HttpResponseMessage。只写 Handle<httprequestexception>()</httprequestexception> 对它完全无效。
必须显式补上结果判断:
- 用
OrResult<httpresponsemessage>(r => r.StatusCode == HttpStatusCode.ServiceUnavailable)</httpresponsemessage> - 更推荐一步到位:直接用
HttpPolicyExtensions.HandleTransientHttpError(),它已内置覆盖 408、429、全部 5xx 和HttpRequestException - 如果业务还定义了 JSON 错误码(如
{"code":500}),得在OrResult后接OrResult<httpresponsemessage>(r => r.Content.ReadAsStringAsync().ContinueWith(t => ParseCode(t.Result) == 500))</httpresponsemessage>——注意要避免阻塞,用ContinueWith或提前解析
WaitAndRetryAsync 的 sleepDurationProvider 别碰 Math.Pow
手写 TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) 看似指数退避,实际会让所有客户端在第 1、2、4、8 秒整点集中重试,下游压力瞬间翻倍。
正确做法是用 Polly 内置抖动算法:
- 优先选
Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(0.5), retryCount: 3) - 它生成的是随机但有边界的延迟,既防雪崩,又保平均响应时间可控
- 别自己 new
Random()放进 lambda —— 多线程下可能重复种子,导致抖动失效
AddPolicyHandler 必须绑定到命名 HttpClient 才生效
直接 new HttpClient() 或从 DI 容器 resolve 出未注册策略的 IHttpClientFactory 实例,Polly 策略根本不会执行。
必须这样注册:
services.AddHttpClient<IOrderService>("order-api")- 然后链式调用
.AddPolicyHandler(GetResilientPolicy()),其中GetResilientPolicy()返回的是IAsyncPolicy<HttpResponseMessage> - 业务代码里通过构造函数注入
IOrderService或IHttpClientFactory获取客户端,而不是手动 new
否则策略只是挂在空气里,请求走的是裸 HttpClient.SendAsync 路径。
Retry 和 CircuitBreaker 组合顺序不能反
Policy.WrapAsync(retry, circuitBreaker) 是错的。这样熔断器只看到第一次调用,后续重试失败不计入统计,failureThreshold 形同虚设。
正确嵌套是:熔断在外、重试在内:
Policy.WrapAsync(circuitBreaker, retry)- 如果还加了
TimeoutPolicy,它必须放在最外层:Policy.WrapAsync(timeout, circuitBreaker, retry) - 所有策略都必须是
IAsyncPolicy<HttpResponseMessage>类型,类型不匹配会导致编译通过但运行时静默跳过
最容易被忽略的是:多个命名客户端默认各自维护独立熔断状态,要共享需把 CircuitBreakerPolicy 注册为单例,并传入同一个实例。


















