共计 1998 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点分析
在 C# 开发中,API 调用是最基础也是最重要的功能之一。但很多开发者在使用过程中经常会遇到各种头疼的问题:

- HttpClient 的资源泄漏:虽然 HttpClient 实现了 IDisposable 接口,但频繁创建和销毁会导致套接字耗尽。我曾经在一个高并发项目中,就因为不当使用导致了 ”SocketException: No such host is known” 的错误
- RestSharp 的版本陷阱:从 v107 开始 RestSharp 进行了不兼容的 API 改动,很多老项目升级时都会遇到问题。更糟的是,NuGet 上同时存在新旧两个主要版本,容易混淆
- 线程安全问题:尤其是使用静态 HttpClient 实例时,如果不注意配置,在多线程环境下可能引发难以排查的异常
主流工具技术对比
| 特性 | HttpClientFactory | RestSharp v107+ | Refit |
|---|---|---|---|
| 依赖注入支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 请求 / 响应拦截 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 序列化性能 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 单元测试友好性 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
核心实现方案
1. 增强型 HttpClient 封装
使用 Polly 实现带指数退避的重试策略:
public class ResilientHttpClient
{
private static readonly IAsyncPolicy<HttpResponseMessage> _retryPolicy =
Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(x => !x.IsSuccessStatusCode)
.WaitAndRetryAsync(3, attempt =>
TimeSpan.FromSeconds(Math.Pow(2, attempt)));
public async Task<string> GetWithRetryAsync(string url)
{using var client = new HttpClient();
var response = await _retryPolicy.ExecuteAsync(() => client.GetAsync(url));
return await response.Content.ReadAsStringAsync();}
}
2. RestSharp 日志中间件
public class LoggingMiddleware : IRestClientMiddleware
{
public async Task<RestResponse> ExecuteAsync(
RestRequest request,
Func<RestRequest, Task<RestResponse>> next)
{var stopwatch = Stopwatch.StartNew();
try
{LogRequest(request);
var response = await next(request);
LogResponse(response, stopwatch.Elapsed);
return response;
}
catch (Exception ex)
{logger.LogError(ex, "API 调用异常");
throw;
}
}
}
性能优化关键点
序列化器对比测试
使用 BenchmarkDotNet 测试不同场景下的性能表现:
| 操作 | System.Text.Json | Newtonsoft.Json |
|---|---|---|
| 简单对象序列化 | 125 ns | 210 ns |
| 复杂对象反序列化 | 1.2 μs | 2.8 μs |
| 大数组处理 | 8 ms | 12 ms |
生产环境建议
-
HttpClient 的 DNS 问题:
var client = new HttpClient(new SocketsHttpHandler {PooledConnectionLifetime = TimeSpan.FromMinutes(5) // ⚠️ 5 分钟刷新 DNS }); -
流式响应处理:
await using var stream = await response.Content.ReadAsStreamAsync(); await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<Item>(stream)) {// 处理单个 item 后立即释放} -
负载测试要点:
- 使用 BenchmarkDotNet 的
[Params]特性测试不同并发级别 - 监控 TCP 连接数(
netstat -ano) - 重点关注 P95 和 P99 响应时间
经验总结
经过多个项目的实践验证,我的工具选型建议是:
- 内部微服务调用:首选 Refit,接口定义清晰,与 ASP.NET Core 集成度最高
- 第三方 API 对接:推荐使用增强封装的 HttpClient,可控性更强
- 遗留系统维护:继续使用 RestSharp v106,但需要严格锁版本
最后提醒:无论选择哪种方案,一定要实现完善的监控和日志,这是后期排查问题的救命稻草。
正文完
发表至: 编程开发
近一天内
