发布于2026-07-06 阅读(0)
扫一扫,手机访问
在实际项目里,把 Refit 和 Polly 整合起来做弹性 HTTP 客户端,看着简单,但真落地时有好几个容易踩的坑。下面就把几个最关键的环节拆开说清楚,从注册策略、指数退避抖动,到重试原因捕捉、超时冲突,每个点都直接对应线上常见的“炸裂”场景。
Refit注册时需通过IHttpClientFactory挂Polly策略:先AddRefitClient,再ConfigureHttpClient,最后AddPolicyHandler;不可直接对Refit接口调用AddPolicyHandler,否则编译错误。

Refit 本身不直接支持策略注入,必须通过 IHttpClientFactory 中间层来实现。核心路径很简单:先用 AddRefitClient 注册 Refit 接口,接着用 AddPolicyHandler 挂上 Polly 策略,最后交给 DI 容器解析出带弹性的客户端。
常见错误是直接对 Refit 接口调用 AddPolicyHandler,这百分百编译报错——因为 Refit 的扩展方法返回的是 IServiceCollection,不是 IHttpClientBuilder,链式根本接不上。
builder.Services .AddRefitClient() .ConfigureHttpClient(client => client.BaseAddress = new Uri("https://api.github.com/")) .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetTimeoutPolicy());
GetRetryPolicy() 必须返回 IAsyncPolicy 类型,不能是泛型 Policy,否则编译就过不去。PolicyWrap 组合,千万不能链式调用两次 AddPolicyHandler(后者会直接覆盖前者,逻辑全白写)。纯指数退避(比如 1s → 2s → 4s)看着挺合理,但在高并发场景下很容易引发“重试风暴”——大量请求在同一个毫秒级时间点集体重试,下游服务瞬间被压垮。所以加随机抖动不是优化项,是生产环境的硬性要求。
举个例子,不加抖动的策略:TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));加抖动后应该改成:TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) + TimeSpan.FromMilliseconds(new Random().Next(100, 300))。
new Random() 在高并发下容易产生重复种子,推荐用 Random.Shared(.NET 6+)或者注入线程安全的 IRandom。WaitAndRetryAsync 的抖动重载,但需要手动传入 sleepDurationProvider,没有一键开关。默认情况下,Refit 抛出的异常(比如 ApiException)只是最终失败结果,中间哪次重试失败、为什么失败,完全看不到。因此必须通过 onRetryAsync 回调来记录细节。
关键点在于:回调里的 outcome.Exception 是原始异常,outcome.Result 是 HttpResponseMessage,两者都可能为 null,一定要做判空处理。
onRetryAsync: (outcome, span, attempt, context) =>{ var ex = outcome.Exception?.InnerException ?? outcome.Exception; var statusCode = outcome.Result?.StatusCode ?? default; Console.WriteLine($"第{attempt}次重试,等待{span.TotalSeconds:F1}s,状态码:{statusCode},异常:{ex?.GetType().Name}");}PolicyWrap(比如重试+熔断),onRetryAsync 只对重试策略生效,熔断触发时走的是 onBreak 回调。会冲突,而且 HttpClient.Timeout 优先级更高——它会在 Polly 的 TimeoutPolicy 触发前就抛出 TaskCanceledException,导致 Polly 超时策略压根不执行。
解决办法也很直接:必须禁用 HttpClient.Timeout,在 ConfigureHttpClient 中设为 TimeSpan.Zero 或者干脆不设置;所有超时逻辑全部交给 Polly 去控制。
.ConfigureHttpClient(c => { c.BaseAddress = ...; c.Timeout = TimeSpan.FromSeconds(10); // ❌ 这会劫持 Polly 超时.ConfigureHttpClient(c => c.BaseAddress = ...); // 不设 Timeout,再用
AddPolicyHandler(Policy.TimeoutAsync(10))。Policy.TimeoutAsync 对 HttpResponseMessage 生效,而 HttpClient.Timeout 对整个 SendAsync 生命周期生效,语义完全不一样。Refit 和 Polly 的组合看着简单,实际落地时最常卡在策略类型匹配、回调空引用、以及 HttpClient 原生超时的隐式干扰上——这些地方不报编译错、也没日志,但运行时行为完全不符合预期。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8