在 Asp.Net Core 中的 https 上下文中调用 HttpClient 时速度很慢

Jul*_*glo 6 c# https asp.net-core

我有一台使用 Asp.Net Core 的服务器,但在使用 HTTPS 时遇到延迟问题。当仅获取少量图像时,每个请求大约需要 20 毫秒来处理。然而,当我同时发出 125 个请求时,使用 HTTP 时,速度会减慢至 30-80 毫秒(可接受),而使用 HTTPS 时,速度会减慢至 130-850 毫秒(不可接受)。

我已经做了一些调试,当我的服务器对 WMS 服务器进行 http 调用时,速度会变慢。

添加自定义套接字处理程序将延迟从 4000-6000 毫秒减少到当前的 130-850 毫秒,这有很大帮助,但延迟仍然有点多。

PS:服务器将做的不仅仅是代理查询,所以我不能简单地使用专用的代理服务器来扮演该角色。

这是服务器代码的最小示例:

using System.Diagnostics.Tracing;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

using var socketHandler = new SocketsHttpHandler()
{
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(10),
    PooledConnectionLifetime = TimeSpan.FromMinutes(10),
    MaxConnectionsPerServer = 1,
};

using var httpClient = new HttpClient(socketHandler);

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton(httpClient);

var app = builder.Build();
app.MapControllers();
app.Run();


[ApiController]
[Route("[controller]")]
public class WMSController : ControllerBase
{
    private readonly HttpClient Client;
    private readonly string BaseUrl;

    public WMSController(HttpClient client)
    {
        Client = client;
        BaseUrl = "http://my-local-hostname.company.internal:10301/WMS";
    }

    [HttpGet(Name = "WMS")]
    public async Task<ActionResult> GetWms()
    {
        var query = HttpContext.Request.QueryString.Value;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        var response = await Client.GetAsync(BaseUrl + query); // This is the slow part.
        stopwatch.Stop();

        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {stopwatch.ElapsedMilliseconds} - {HttpContext.Connection.Id}");
        stopwatch.Reset();
        var image = await response.Content.ReadAsByteArrayAsync();

        return new FileContentResult(image, "image/jpeg");
    }
}
Run Code Online (Sandbox Code Playgroud)

Cha*_*ace 2

您可以通过多种方式提高效率。

  • 使用全局HttpClient和处理程序,以便对同一服务器的请求可以复用。这也可以防止套接字耗尽。
static HttpClient httpClient = new HttpClient(new SocketsHttpHandler()
{
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(10),
    PooledConnectionLifetime = TimeSpan.FromMinutes(10),
});
Run Code Online (Sandbox Code Playgroud)
  • 使用HttpCompletionOption.ResponseHeadersRead时仅缓冲标头,否则必须缓冲完整响应。
    请注意,计时现在仅在收到标头之前进行。
[HttpGet(Name = "WMS")]
public async Task<ActionResult> GetWms()
{
    var query = HttpContext.Request.QueryString.Value;

    var stopwatch = Stopwatch.StartNew();
    using var response = await Client.GetAsync(BaseUrl + query, HttpCompletionOption.ResponseHeadersRead);

    stopwatch.Stop();
    Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {stopwatch.ElapsedMilliseconds} - {HttpContext.Connection.Id}");
    stopwatch.Reset();

    var image = await response.Content.ReadAsByteArrayAsync();
    return new FileContentResult(image, "image/jpeg");
}
Run Code Online (Sandbox Code Playgroud)
  • 仅当您的用例支持时,进一步的改进才可能有效:将响应作为流直接提供给FileStreamResult
[HttpGet(Name = "WMS")]
public async Task<ActionResult> GetWms()
{
    var query = HttpContext.Request.QueryString.Value;

    var stopwatch = Stopwatch.StartNew();
    // NO using, otherwise stream gets disposed
    var response = await Client.GetAsync(BaseUrl + query, HttpCompletionOption.ResponseHeadersRead);

    stopwatch.Stop();
    Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {stopwatch.ElapsedMilliseconds} - {HttpContext.Connection.Id}");
    stopwatch.Reset();

    var image = await response.Content.ReadAsStreamAsync();
    return new FileStreamResult(image, "image/jpeg");
}
Run Code Online (Sandbox Code Playgroud)