waitandretryasync 合理配置为 retrycount: 3 起步、指数退避(如 timespan.fromseconds(math.pow(2, retryattempt)))、叠加 ±10% jitter 避免请求洪峰,并严格确保退避时间大于 httpclient.timeout;仅对瞬态异常(如 httprequestexception、timeoutexception)重试,排除业务异常;须用 policy.wrapasync 嵌套熔断器,且通过带 cancellationtoken 和 context 的重载保障上下文透传与可观测性。

直接用 Policy.Handle<texception>().WaitAndRetryAsync</texception>,别手写重试循环——它不处理并发竞争、退避抖动、超时嵌套和策略组合失效问题。
WaitAndRetryAsync 的重试次数与退避策略怎么配才合理
重试不是越多越好,关键在“错峰试探”而非“反复强刷”。默认指数退避(Math.Pow(2, retryAttempt) * 100)适合大多数 HTTP 调用,但要注意三点:
-
retryCount: 3是常见起点,超过 5 次基本说明下游已不可恢复,该走熔断而不是重试 -
sleepDurationProvider返回的TimeSpan必须严格大于底层 HttpClient 的Timeout,否则重试还没发出去就被取消,抛出OperationCanceledException却不触发重试逻辑 - 若依赖服务有明确的“瞬时过载”特征(如数据库连接池打满),可加 jitter:在退避时间上叠加随机毫秒(±10%),避免重试请求同时涌向下游
重试时如何区分“该重试”和“不该重试”的异常
盲目重试会放大问题。比如 HttpRequestException 可能因网络抖动引起,适合重试;但 ArgumentException 或 InvalidOperationException 是代码逻辑错误,重试毫无意义。
- 只对瞬态故障重试:
Handle<httprequestexception>()</httprequestexception>、Handle<timeoutexception>()</timeoutexception>、OrResult<httpresponsemessage>(r => r.StatusCode == HttpStatusCode.RequestTimeout)</httpresponsemessage> - 显式排除业务异常:
.Or<invalidorderexception>().Or<insufficientbalanceexception>()</insufficientbalanceexception></invalidorderexception>,避免把用户输入错误当网络问题重试 - 如果用了
HttpClient+IHttpClientFactory,注意HttpRequestException本身可能包装了SocketException或 DNS 失败,这些都属于可重试范畴
重试 + 熔断组合时,为什么熔断器总不打开
常见现象是重试跑满了 3 次全失败,但 CircuitBreakerPolicy 没触发——根本原因是没把重试策略包进熔断策略里,而是两个独立策略串行执行。
- 错误写法:
AddPolicyHandler(retryPolicy).AddPolicyHandler(circuitBreakerPolicy)→ 重试失败后才进熔断,熔断统计的是“重试后的最终结果”,不是单次调用失败数 - 正确写法:用
Policy.WrapAsync显式嵌套:Policy.WrapAsync(retryPolicy, circuitBreakerPolicy),确保每次重试都计入熔断滑动窗口 - 更推荐注册命名客户端时直接绑定组合策略:
AddPolicyHandler((sp, req) => Policy.WrapAsync(retry, circuit)),避免手动ExecuteAsync绕过策略生命周期
异步重试中如何安全传递上下文(如 TraceId、CancellationToken)
WaitAndRetryAsync 默认不透传 CancellationToken,且重试过程丢失原始请求上下文,导致日志链路断裂或取消信号被忽略。
- 必须用带
cancellationToken参数的重载:WaitAndRetryAsync(..., onRetry: (e, ts, ctx) => { ... }),并在回调里记录ctx["TraceId"] - 业务方法签名需支持
CancellationToken,并在调用链路每一层都向下传递,不能只在最外层接收 - 避免在
onRetry回调里做耗时操作(如写 DB、发 MQ),它运行在重试调度线程上,阻塞会导致整个重试流程卡住
真正难的不是写对第一个重试策略,而是确认重试是否被真实执行、失败是否被准确归因、重试之间是否互相干扰——这些都得靠日志里的 Context["RetryCount"] 和熔断器的 HalfOpen 状态观测来验证,光看成功/失败计数容易误判。











