大模型API的响应时间普遍较长,动辄数秒到几十秒,再加上服务商限流、网络抖动,稍不注意就会把系统拖垮。很多.NET Core开发者在调用AI API时仍然直接new HttpClient(),这种写法不仅会导致套接字耗尽,还缺少重试和熔断保护。本文将结合HttpClientFactory与Polly,搭建一套健壮的AI API调用方案。

一、为什么不能用new HttpClient()
直接实例化HttpClient存在一个经典问题:HttpClient在Dispose之后,底层套接字并不会立即释放,而是进入TIME_WAIT状态,持续占用端口。在高频调用场景下(比如批量处理文本向量化),短时间内大量创建和销毁HttpClient,会导致端口耗尽,出现SocketException。相反,如果使用单个静态HttpClient实例,又会因为HttpClient不感知DNS变更,在服务端切换IP后持续连到旧地址。
HttpClientFactory通过内部维护Handler池来解决这两点:它复用HttpClientHandler,避免套接字频繁创建;同时默认每2分钟轮换一次handler,保证DNS能被正确刷新。因此在ASP.NET Core及任何使用依赖注入的.NET Core项目中,推荐始终通过IHttpClientFactory获取HttpClient实例。
二、注册命名客户端与基础配置
调用AI API通常有固定的BaseAddress和ApiKey,适合用命名客户端封装。在Program.cs或Startup中注册:
builder.Services.AddHttpClient("AiClient", client =>
{
client.BaseAddress = new Uri("https://api.ipipp.com/v1/");
client.Timeout = TimeSpan.FromSeconds(120); // AI接口响应慢,超时放宽
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
});
注意AI API的超时设置要明显大于普通接口。流式输出(SSE)场景下,HttpClient的Timeout属性会在整个请求期间生效,如果生成内容很长,120秒甚至更长都是合理的。也可以将Timeout设为Timeout.InfiniteTimeSpan,改用CancellationTokenSource自行控制取消时机。
在业务代码中通过IHttpClientFactory创建客户端:
public class ChatService
{
private readonly IHttpClientFactory _factory;
public ChatService(IHttpClientFactory factory) => _factory = factory;
public async Task<string> AskAsync(string prompt)
{
var client = _factory.CreateClient("AiClient");
var resp = await client.PostAsJsonAsync("chat/completions",
new { model = "gpt-4", messages = new[] { new { role = "user", content = prompt } } });
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsStringAsync();
}
}
三、用Polly实现重试与熔断
AI服务商的限流通常返回429状态码,瞬时故障返回500或503。对这类可重试错误,用Polly的WaitAndRetryAsync做指数退避重试;对持续性故障,用CircuitBreakerAsync熔断,避免反复打向已经瘫痪的服务引发雪崩。需要先安装Microsoft.Extensions.Http.Polly包。
builder.Services.AddHttpClient("AiClient", client => { /* 同上配置 */ })
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError() // 5xx、408、HttpRequestException
.OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))))
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30)));
策略的执行顺序是后注册的先执行。上面的配置中,熔断器在最外层,重试在里层:请求先经过熔断器判断,若熔断器处于Open状态则直接抛出BrokenCircuitException,根本不会发起HTTP请求;若熔断器关闭,则进入重试逻辑。捕获该异常后可以走降级逻辑,例如返回缓存的答案或提示用户稍后再试。
429响应通常带有Retry-After响应头,简单的指数退避可以进一步优化为读取该头动态决定等待时间。另外,熔断阈值要根据实际QPS设置:QPS低的内部工具类服务,5次失败就熔断30秒完全够用;高并发网关则应适当调大统计阈值,避免误熔断。
四、降级兜底与监控建议
熔断打开后必须有兜底方案。常见做法是Fallback策略捕获所有异常,返回一个默认结果或切换到备用模型、备用供应商。Polly支持在策略链末尾追加FallbackAsync:
var fallback = Policy<HttpResponseMessage>
.Handle<BrokenCircuitException>()
.Or<HttpRequestException>()
.FallbackAsync(
fallbackAction: ct => Task.FromResult(
new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)
{
Content = new StringContent("{\"error\":\"服务暂时不可用,请稍后重试\"}")
}),
onFallback: (result, ctx) =>
{
// 记录日志、上报指标
return Task.CompletedTask;
});
运维层面建议在onFallback和onRetry回调中打点,把重试次数、熔断事件输出到日志或APM系统。熔断状态变化还可以通过onBreak、onReset事件订阅并接入告警。对于多实例部署的应用,如果限流是按API Key维度统计的,可以考虑用分布式令牌桶控制重试压力,避免多个实例同时重试把限流窗口打满。
总结来说,HttpClientFactory解决的是连接管理层面的稳定性,Polly解决的是故障场景下的弹性,两者配合再加上合理的超时设置和降级设计,才能让.NET Core应用在调用AI API时既高效又可靠。
.NET CoreHttpClientFactoryPolly熔断策略修改时间:2026-08-31 21:21:05