polly 的 hedging 策略是在主请求超时或失败前主动发起备份请求,任一成功即取消其余请求并采用其响应;它解决单点延迟抖动问题,要求 httpclient 支持 cancellationtoken 且 api 幂等。

什么是 Polly 的 Hedging 策略
Hedging 不是并发发多个请求然后“等全部返回再挑最快的”,而是:在主请求响应超时或失败前,主动发起一个或多个备份请求;一旦任一请求成功返回,就立即取消其余未完成的请求,并使用该响应。它解决的是“单点延迟抖动”问题,比如某个服务实例临时卡顿、网络短暂拥塞。
注意:Hedging 依赖底层 HttpClient 支持取消(即使用 CancellationToken),且目标 API 必须是幂等的——因为可能有多个请求实际到达服务端。
如何配置 AddHedgingPolicy 并集成到 HttpClient
从 Polly v8 开始,Hedging 是独立策略,需显式添加。它不和 Retry 或 Timeout 自动组合,必须手动链式调用。
- 必须先配置
Timeout策略作为 hedging 的触发条件(即“主请求多久没回就发备份”) -
hedgingDelay参数决定备份请求的延迟时间(例如TimeSpan.FromMilliseconds(100)),不是并发发起,而是错峰降低雪崩风险 -
maxHedgedAttempts包含原始请求,所以设为3表示最多总共尝试 3 次(1 次原 + 2 次备份) - 必须用
AddPolicyHandler注册到IHttpClientBuilder,不能只靠AddTransientHttpErrorPolicy
示例注册代码:
services.AddHttpClient<MyApiClient>()
.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(3)))
.AddPolicyHandler(Policy.HedgingAsync<HttpResponseMessage>(
maxHedgedAttempts: 3,
hedgingDelay: TimeSpan.FromMilliseconds(100),
onHedging: (ev) => {
Console.WriteLine($"Hedging attempt {ev.AttemptNumber} for {ev.OriginalRequest.RequestUri}");
},
policy: Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(new[] { TimeSpan.Zero })
));
Hedging 下的响应取消与资源清理关键点
很多人以为只要用了 Hedging 就自动取消其他请求——其实不然。Polly 仅负责调用 HttpClient.SendAsync(..., cancellationToken) 时传入的 token,是否真正中断连接,取决于:
-
HttpClient实例是否复用(推荐用IHttpClientFactory,避免 socket 耗尽) - 目标服务器是否尊重
Connection: close或 TCP RST - .NET 版本:.NET 5+ 对
HttpClient取消支持更可靠;.NET Core 3.1 及以前在某些 TLS 场景下可能无法及时中断 - 你自己的
HttpRequestMessage是否设置了Content且未设置Headers.ContentLength?流式上传内容可能导致取消滞后
建议在业务代码中始终显式检查 response.IsSuccessStatusCode,并用 response.Content.ReadAsStringAsync() 后尽快释放,不要长期持有 HttpContent 流。
为什么不用 Task.WhenAny 手写“最快响应”逻辑
直接用 Task.WhenAny 发多个 HttpClient.SendAsync 看似简单,但会踩一堆坑:
- 没有内置超时控制,需额外套
Task.WhenAny+Task.Delay,逻辑复杂且易漏 cancel - 无法统一处理重试、熔断、降级等策略,和 Polly 生态割裂
- 所有请求都真实发出,无延迟错峰,可能把下游打挂(尤其非幂等操作)
- 无法感知哪个是“主请求”,日志、指标、链路追踪难以对齐
- 手动取消时,
HttpClient的Dispose和 socket 复用行为不可控,容易引发SocketException或连接泄漏
Hedging 是 Polly 对这类场景的封装抽象,它把“延迟发起 + 取消剩余 + 结果选择”收拢成可配置、可观测、可组合的行为。手写反而更容易出错,也难维护。
真正需要关注的,是 hedging 延迟值怎么定、最大尝试次数是否合理、以及后端接口是否真的幂等——这些比“怎么写语法”重要得多。










