HttpClient 实例必须复用,不能每次 new
在高并发场景下,频繁
new HttpClient()会导致端口耗尽、DNS 缓存失效、连接池无法复用,最终抛出
SocketException: Too many open files或
HttpRequestException: Connection refused。.NET 的
HttpClient是线程安全的,设计初衷就是长期复用——它内部管理着
HttpMessageHandler(如
HttpClientHandler)和底层连接池。
错误写法:
public string Get(string url)
{
using var client = new HttpClient(); // ❌ 每次都新建,灾难性
return client.GetStringAsync(url).Result;
}
正确做法:
全局单例:在 .NET Core 6+ 中推荐注册为Singleton服务,或直接定义
static readonly HttpClient避免在
using中包裹
HttpClient,否则会提前释放
HttpMessageHandler,破坏连接复用 若需不同配置(如超时、证书),应复用
HttpClientHandler实例,而非
HttpClient实例
不要手动设置 HttpClient.Timeout 用于单请求控制
HttpClient.Timeout是实例级属性,修改它会影响后续所有请求,且不是线程安全的。高并发下多个线程同时改
Timeout会引发不可预测行为,还可能覆盖彼此的设置。
正确做法是使用
CancellationToken控制单次请求超时:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
var response = await client.GetAsync("https://api.example.com", cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
// 超时处理
}
HttpClient.Timeout应只在初始化时设一次(如 100 秒),作为兜底值 单请求超时必须走
CancellationToken,这是唯一安全、可取消、可并发的方案 注意:不要把同一个
CancellationTokenSource复用于多个并发请求,应每个请求新建或从池中获取
自定义 HttpClientHandler 时务必启用连接池并设合理参数
默认
HttpClientHandler的连接池行为在高并发下常被忽视。例如
MaxConnectionsPerServer默认是
int.MaxValue(.NET 5+),但旧版本(.NET Framework / .NET Core 2.x)默认仅 2,极易成为瓶颈;
AutomaticDecompression若未开启,压缩响应体将无法自动解压;
UseProxy和
ServerCertificateCustomValidationCallback若配置不当,也会引入延迟或 TLS 握手失败。
推荐初始化方式:
var handler = new HttpClientHandler
{
MaxConnectionsPerServer = 100,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
UseProxy = false,
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
var client = new HttpClient(handler);
MaxConnectionsPerServer需根据目标域名数量和 QPS 估算,通常 50–200 是较安全起点 禁用代理(
UseProxy = false)可省去 DNS 查询和代理协商开销 自定义证书验证回调必须明确写出,不能依赖默认行为(尤其在容器或非标准 TLS 环境中) 若使用
SocketsHttpHandler(.NET Core 2.1+ 默认),还可进一步调优
IdleConnectionTimeout和
PooledConnectionLifetime
DI 容器中注册 HttpClient 时别漏掉 IHttpClientFactory
直接注册
HttpClient单例看似简单,但丢失了生命周期管理、命名客户端、策略注入(如 Polly 重试)、日志与指标集成等关键能力。.NET Core 内置的
IHttpClientFactory不是“创建 HttpClient 的工厂”,而是“管理 HttpClientHandler 生命周期 + 提供策略扩展”的中枢。
注册方式(Startup.cs 或 Program.cs):
services.AddHttpClient("github", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.UserAgent.ParseAdd("my-app/1.0");
})
.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(100))); // Polly
命名客户端(如 "github")让不同业务模块隔离配置,避免互相干扰
AddHttpClient注册的是逻辑客户端,底层仍复用有限的
HttpMessageHandler实例池 不传名字的
AddHttpClient<tclient>()</tclient>也适用,但必须配合
IServiceProvider获取,不能直接 new 切勿在 DI 中注册
HttpClient类型本身——这会让框架误以为你要手动管理它的生命周期
最常被忽略的一点:即使用了
IHttpClientFactory,如果在代码里又对返回的
HttpClient做
using或手动
Dispose,依然会提前终结底层 handler,导致连接池失效。工厂返回的
HttpClient实例本就应视为“即取即用、无需释放”的轻量对象。
