共计 2714 个字符,预计需要花费 7 分钟才能阅读完成。
在 C# 开发中,API 调用是几乎每个项目都会涉及的基础功能。但看似简单的 HTTP 请求,却隐藏着许多陷阱。本文将分享我在实际项目中踩过的坑,以及总结出的最佳实践方案。

1. 为什么要关注 API 调用工具选型?
最近在做一个电商后台项目时,我遇到了一个诡异的问题:系统运行几小时后,就会开始出现 SocketException。经过排查,发现问题出在不当使用 HttpClient 上——每个请求都 new 一个 HttpClient,导致端口耗尽。
常见痛点分析
- HttpClient 的 Dispose 陷阱 :虽然实现了 IDisposable,但实际应该复用实例
- RestSharp 的版本地震 :v107 的破坏性变更让很多老代码无法直接升级
- 线程安全问题 :高频调用时若未正确同步,会导致响应内容错乱
- DNS 缓存问题 :长期运行的 HttpClient 不会刷新 DNS,可能导致连接失败
2. 主流工具技术对比
| 特性 | HttpClientFactory | RestSharp v107 | Refit |
|---|---|---|---|
| 依赖注入支持 | ✅ 原生支持 | ✅ 需手动配置 | ✅ 完美 |
| 请求拦截 | ✅ DelegatingHandler | ✅ 中间件 | ❌ |
| 响应拦截 | ✅ 同上 | ✅ 中间件 | ❌ |
| JSON 序列化性能 | ⚠️ 依赖实现 | ⚠️ 较慢 | ✅ 快 |
| 单元测试友好度 | ✅ 可 Mock | ❌ 较难 | ✅ 容易 |
3. 核心实现方案
3.1 使用 HttpClientFactory + Polly 实现稳健调用
Polly 是.NET 生态中最成熟的弹性策略库,特别适合处理网络波动。以下是带指数退避的重试策略实现:
// 在 Startup.cs 中配置
services.AddHttpClient("RetryClient")
.AddTransientHttpErrorPolicy(policy =>
policy.WaitAndRetryAsync(
3,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
));
3.2 RestSharp 的日志中间件
新版 RestSharp 采用中间件管道设计,可以方便地插入日志记录:
var client = new RestClient(options)
{
ConfigureMessageHandler = handler =>
{handler.AddMiddleware(new LoggingMiddleware());
}
};
class LoggingMiddleware : Middleware
{public override async Task<RestResponse> ExecuteAsync(...)
{logger.LogDebug($"Request: {request.Method} {request.Resource}");
var response = await base.ExecuteAsync(...);
logger.LogDebug($"Response: {response.StatusCode}");
return response;
}
}
3.3 序列化性能对比
通过 BenchmarkDotNet 测试(单位:μs/op):
| 操作 | System.Text.Json | Newtonsoft.Json |
|---|---|---|
| 简单对象序列化 | 0.45 | 0.92 |
| 复杂对象序列化 | 2.1 | 3.8 |
| 大数组反序列化 | 15.3 | 28.7 |
4. 关键代码示例
4.1 线程安全的 HttpClient 单例
public class SafeHttpClient
{private static readonly Lazy<HttpClient> _client = new(() =>
{var client = new HttpClient();
// ⚠️ 生产环境必须设置合理超时
client.Timeout = TimeSpan.FromSeconds(30);
return client;
});
public static HttpClient Instance => _client.Value;
// 显式关闭连接池(特殊场景使用)public static void Cleanup() => _client.Value.Dispose();
}
4.2 RestSharp 的 JWT 自动刷新
public class AuthAwareClient
{
private RestClient _client;
private DateTime _tokenExpiry;
public async Task<RestResponse> ExecuteWithAuthAsync(RestRequest request)
{if(DateTime.Now > _tokenExpiry)
{var newToken = await RefreshTokenAsync();
request.AddHeader("Authorization", $"Bearer {newToken}");
}
var response = await _client.ExecuteAsync(request);
// 处理 401 并重试
if(response.StatusCode == HttpStatusCode.Unauthorized)
{// ... 刷新逻辑}
return response;
}
}
5. 生产环境建议
-
DNS 刷新策略 :对于长时间运行的 HttpClient,建议配置
var handler = new SocketsHttpHandler {PooledConnectionLifetime = TimeSpan.FromMinutes(15) }; -
负载测试 :使用 BenchmarkDotNet 模拟高并发
[MemoryDiagnoser] public class ApiBenchmarks {[Benchmark] public async Task CallApi() { /*...*/} } -
流式响应注意事项 :使用 IAsyncEnumerable 时务必及时 Dispose
await using var stream = await response.Content.ReadAsStreamAsync(); await foreach(var item in JsonSerializer.DeserializeAsyncEnumerable<Item>(stream)) {// 处理逻辑}
6. 总结建议
经过多个项目的实践验证,我的工具选型建议是:
- 常规 Web 项目 :优先采用 HttpClientFactory + Polly 组合
- 需要快速原型 :Refit 的声明式接口能极大提升开发效率
- 遗留系统维护 :RestSharp v107 虽然需要适配,但仍是稳定选择
最后提醒:无论选择哪种方案,一定要配套完善的监控和日志,这样才能在出现问题时快速定位。API 调用作为系统间通信的桥梁,其稳定性直接影响整体业务,值得投入时间做好加固。
正文完
