Polly调用ExecuteAsync时,HttpClient实例应创建在委托内还是外?
你当前的Post写法逻辑能跑通但有性能隐患,Get写法存在致命逻辑bug会导致重试失效,两种写法都有优化空间。
现有写法的问题
1. Get写法的致命bug
你的重试策略触发时,会先执行Authenticate()更新全局_loginToken,但因为HttpClient实例创建在委托外部,第一次请求时已经给client.DefaultRequestHeaders添加了旧的Token。而你的UpdateClientHeader逻辑是仅当Headers中不存在Token时才添加,重试时不会替换成新Token,第二次请求依然携带旧Token,必然再次返回401,重试完全失效。
同时直接修改HttpClient.DefaultRequestHeaders不是线程安全操作,多线程复用同一个客户端时会出现请求头错乱的问题。
2. Post写法的性能缺陷
虽然每次重试都会新建HttpClient实例,能拿到最新的Token,逻辑可以跑通,但频繁创建销毁HttpClient会导致底层socket资源无法及时复用,大量连接进入TIME_WAIT状态,高并发场景下会出现socket耗尽、请求失败、吞吐量下降的问题,属于典型的HttpClient错误用法。
最优解决方案
HttpClient本身设计为可全局复用的单例实例,不需要每次请求新建,结合你的业务场景改造方式如下:
第一步:全局复用HttpClient实例
提前创建静态HttpClient实例全链路复用,避免重复实例化的开销:
// 全局复用,仅初始化一次 private static readonly HttpClient _sharedClient = new HttpClient(new NativeMessageHandler()) { Timeout = TimeSpan.FromSeconds(TimeOutSec) };
第二步:请求头绑定到单次请求而非客户端
不要修改HttpClient.DefaultRequestHeaders,改为把Token、correlationId等头信息绑定到单次HttpRequestMessage上,既没有线程安全问题,重试时也会自动读取最新的_loginToken:
// Get请求示例,Post仅需修改HttpMethod和赋值Content即可 var httpResponse = await AuthenticationOnUnauthorizePolicy.ExecuteAsync(async () => { using var request = new HttpRequestMessage(HttpMethod.Get, url); // 给当前请求单独加头,不影响全局客户端 if (_loginToken != null) { request.Headers.TryAddWithoutValidation("Token", _loginToken); } request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(HttpConsts.JsonMediaType)); request.Headers.TryAddWithoutValidation("CorrelationId", correlationId); WriteNetworkAccessStatusToLog(); return await _sharedClient.SendAsync(request, token); });
额外优化点
你的Authenticate方法中单独创建的HttpClient也可以替换为上述全局复用的实例,进一步减少资源消耗。
关于Polly Context的疑问
你当前的场景不需要使用Context传递客户端实例,直接复用全局HttpClient即可,现有策略逻辑不需要修改即可正常工作。
内容的提问来源于stack exchange,提问作者o_w

