商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > C#怎么使用Refit和Polly组合_C#弹性HTTP客户端教程【推荐】

C#怎么使用Refit和Polly组合_C#弹性HTTP客户端教程【推荐】

  发布于2026-07-06 阅读(0)

扫一扫,手机访问

在实际项目里,把 Refit 和 Polly 整合起来做弹性 HTTP 客户端,看着简单,但真落地时有好几个容易踩的坑。下面就把几个最关键的环节拆开说清楚,从注册策略、指数退避抖动,到重试原因捕捉、超时冲突,每个点都直接对应线上常见的“炸裂”场景。

Refit注册时需通过IHttpClientFactory挂Polly策略:先AddRefitClient,再ConfigureHttpClient,最后AddPolicyHandler;不可直接对Refit接口调用AddPolicyHandler,否则编译错误。

C#怎么使用Refit和Polly组合_C#弹性HTTP客户端教程【推荐】

Refit注册时怎么挂Polly策略

Refit 本身不直接支持策略注入,必须通过 IHttpClientFactory 中间层来实现。核心路径很简单:先用 AddRefitClient 注册 Refit 接口,接着用 AddPolicyHandler 挂上 Polly 策略,最后交给 DI 容器解析出带弹性的客户端。

常见错误是直接对 Refit 接口调用 AddPolicyHandler,这百分百编译报错——因为 Refit 的扩展方法返回的是 IServiceCollection,不是 IHttpClientBuilder,链式根本接不上。

  • 正确写法(.NET 6+ Program.cs):
    builder.Services    .AddRefitClient()    .ConfigureHttpClient(client => client.BaseAddress = new Uri("https://api.github.com/"))    .AddPolicyHandler(GetRetryPolicy())    .AddPolicyHandler(GetTimeoutPolicy());
  • GetRetryPolicy() 必须返回 IAsyncPolicy 类型,不能是泛型 Policy,否则编译就过不去。
  • 如果同时需要熔断 + 重试,要用 PolicyWrap 组合,千万不能链式调用两次 AddPolicyHandler(后者会直接覆盖前者,逻辑全白写)。

为什么指数退避要加抖动(jitter)

纯指数退避(比如 1s → 2s → 4s)看着挺合理,但在高并发场景下很容易引发“重试风暴”——大量请求在同一个毫秒级时间点集体重试,下游服务瞬间被压垮。所以加随机抖动不是优化项,是生产环境的硬性要求。

举个例子,不加抖动的策略:TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));加抖动后应该改成:TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) + TimeSpan.FromMilliseconds(new Random().Next(100, 300))

  • 抖动值建议控制在 100–500ms 区间,太大把退避效果削弱了,太小又起不到错峰作用。
  • new Random() 在高并发下容易产生重复种子,推荐用 Random.Shared(.NET 6+)或者注入线程安全的 IRandom
  • Polly.Extensions.Http 提供了 WaitAndRetryAsync 的抖动重载,但需要手动传入 sleepDurationProvider,没有一键开关。

Refit + Polly 下如何捕获具体重试原因

默认情况下,Refit 抛出的异常(比如 ApiException)只是最终失败结果,中间哪次重试失败、为什么失败,完全看不到。因此必须通过 onRetryAsync 回调来记录细节。

关键点在于:回调里的 outcome.Exception 是原始异常,outcome.ResultHttpResponseMessage,两者都可能为 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}");}
  • 不要在回调里 throw 新异常,否则会中断重试流程,直接变成失败。
  • 如果用了 PolicyWrap(比如重试+熔断),onRetryAsync 只对重试策略生效,熔断触发时走的是 onBreak 回调。

超时策略和 HttpClient.Timeout 冲突吗

会冲突,而且 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.TimeoutAsyncHttpResponseMessage 生效,而 HttpClient.Timeout 对整个 SendAsync 生命周期生效,语义完全不一样。

Refit 和 Polly 的组合看着简单,实际落地时最常卡在策略类型匹配、回调空引用、以及 HttpClient 原生超时的隐式干扰上——这些地方不报编译错、也没日志,但运行时行为完全不符合预期。

本文转载于:https://www.php.cn/faq/2444805.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注