You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET HttpClient.SendAsync调用缓慢问题排查求助

REST API调用性能排查:服务器与服务端间请求延迟问题

问题场景

系统架构为客户端→服务器→服务端,整体功能正常,但服务器与服务端间的请求耗时过长(1-1.5秒),服务端本身响应速度很快。使用NSwag生成的GeocodingClient类调用服务,经测量var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);这一行执行缓慢,其他环节无异常。

服务端日志显示请求到达时间存在延迟,但用Postman直接调用该服务时响应快速正常。此外,结构类似的其他服务调用运行流畅。

生成的GeocodingClient类代码

[System.CodeDom.Compiler.GeneratedCode("NSwag",
    "13.0.5.0 (NJsonSchema v10.0.22.0 (Newtonsoft.Json v11.0.0.0))")]
public partial class GeocodingClient 
{
    private string _baseUrl = "";
    private System.Net.Http.HttpClient _httpClient;
    private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;

    public GeocodingClient(string baseUrl, System.Net.Http.HttpClient httpClient)
    {
        BaseUrl = baseUrl; 
        _httpClient = httpClient; 
        _settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(() => 
        {
            var settings = new Newtonsoft.Json.JsonSerializerSettings();
            UpdateJsonSerializerSettings(settings);
            return settings;
        });
    }

    public string BaseUrl 
    {
        get { return _baseUrl; }
        set { _baseUrl = value; }
    }

    //...
}

相关调用方法代码

/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<GetCanalizationLocationsBetweenAreaResponse> GetCanalizationLocationsBetweenAreaAsync(GetCanalizationLocationsBetweenAreaRequest body, System.Threading.CancellationToken cancellationToken)
{
    var urlBuilder_ = new System.Text.StringBuilder();
    urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/TechnicalObjectQuery/GetCanalizationLocationsBetweenArea");

    var client_ = _httpClient;
    try
    {
        using (var request_ = new System.Net.Http.HttpRequestMessage())
        {
            var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
            content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
            request_.Content = content_;
            request_.Method = new System.Net.Http.HttpMethod("POST");
            request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));

            PrepareRequest(client_, request_, urlBuilder_);
            var url_ = urlBuilder_.ToString();
            request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
            PrepareRequest(client_, request_, url_);

            var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            try
            {
                var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
                if (response_.Content != null && response_.Content.Headers != null)
                {
                    foreach (var item_ in response_.Content.Headers)
                        headers_[item_.Key] = item_.Value;
                }

                ProcessResponse(client_, response_);

                var status_ = ((int)response_.StatusCode).ToString();
                if (status_ == "200") 
                {
                    var objectResponse_ = await ReadObjectResponseAsync<GetCanalizationLocationsBetweenAreaResponse>(response_, headers_).ConfigureAwait(false);
                    return objectResponse_.Object;
                }
                else
                if (status_ != "200" && status_ != "204")
                {
                    var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); 
                    throw new ApiException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null);
                }

                return default(GetCanalizationLocationsBetweenAreaResponse);
            }
            finally
            {
                if (response_ != null)
                    response_.Dispose();
            }
        }
    }
    finally
    {
    }
}

问题原因与解决方案

可能原因

  • HttpClient实例未复用:每次调用创建新的HttpClient会导致TCP连接无法复用,每次请求都要重新建立TCP连接(三次握手),大幅增加延迟。Postman默认复用连接,因此速度更快。
  • DNS解析延迟:服务器端DNS缓存未配置或缓存时长过短,导致每次请求都重新解析DNS;而Postman可能已缓存解析结果。
  • 请求头差异:NSwag生成的请求携带的头与Postman不一致,可能触发服务端额外的校验或处理逻辑,导致延迟。
  • 代理设置不一致:应用程序使用的代理与Postman不同,请求路由走了更慢的链路。
  • TLS握手未复用:HttpClient未配置TLS会话复用,每次请求都重新完成TLS握手,增加耗时。

解决方案

  1. 复用HttpClient实例
    确保整个应用生命周期内复用同一个HttpClient实例,可通过依赖注入注册为单例:

    services.AddSingleton<GeocodingClient>(sp => 
    {
        var httpClient = sp.GetRequiredService<HttpClient>();
        httpClient.BaseAddress = new Uri("你的服务基础地址");
        return new GeocodingClient("你的服务基础地址", httpClient);
    });
    
  2. 配置DNS缓存
    在HttpClientHandler中设置DNS缓存时长,减少解析次数:

    var handler = new HttpClientHandler
    {
        DnsRefreshTimeout = TimeSpan.FromMinutes(30) // 根据实际情况调整缓存时长
    };
    var httpClient = new HttpClient(handler);
    
  3. 对齐请求头
    用抓包工具(如Fiddler、Wireshark)对比Postman和应用的请求头,在PrepareRequest方法中调整应用的请求头,确保与Postman一致(比如User-Agent、Accept-Encoding等)。

  4. 统一代理设置
    检查并调整应用的代理配置,确保与Postman一致,必要时关闭不必要的代理:

    var handler = new HttpClientHandler
    {
        UseProxy = false,
        // 若需使用代理,配置正确地址
        // Proxy = new WebProxy("http://你的代理地址:端口")
    };
    
  5. 启用TLS会话复用
    配置HttpClientHandler启用TLS会话复用,减少握手耗时:

    var handler = new HttpClientHandler
    {
        SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13,
        EnableTlsSessionResumption = true
    };
    
  6. 检查PrepareRequest方法
    查看自动生成的PrepareRequest方法是否包含额外逻辑(如自定义头添加、请求修改),这些逻辑可能导致不必要的延迟。


内容的提问来源于stack exchange,提问作者Fovezer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 03:29:53