在Asp.Net Core中在https上下文中调用HttpClient速度较慢。

6
我有一个使用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");
    }
}

就HTTPS而言:尝试将服务器证书缓存为操作系统中的受信任根,可能还要禁用该URL的吊销检查。 - Charlieface
此外,您的网络连接有多大(带宽)?与服务器之间的往返延迟 ping 时间是多少?服务器和客户端是否支持 HTTP/2 或 TLS 1.3?请告诉我。 - Charlieface
客户端和两个服务器都在同一台物理机器上(我也尝试过不同的机器)。往返 ping 时间小于 1 毫秒。客户端和代理服务器都支持 HTTP/2 和 TLS 1.3。 - Julian Silden Langlo
1
啊,如果都是本地的话,那就完全不同了。很可能在 ASP 堆栈中存在其他的低效率问题(或者你正在使用的其他源服务器应用程序中存在问题),或者有另一个代理或防病毒软件在干扰。 - Charlieface
“MaxConnectionsPerServer” 绝对不应该被设置。 - Charlieface
显示剩余3条评论
1个回答

2

您可以提高效率的方法有很多。

  • 使用全局 HttpClient 和处理程序,以便对同一服务器的请求进行多路复用。这也可以防止套接字耗尽。
static HttpClient httpClient = new HttpClient(new SocketsHttpHandler()
{
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(10),
    PooledConnectionLifetime = TimeSpan.FromMinutes(10),
});
  • 使用 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");
}
  • 如果您的使用情况支持,进一步的改进可能只能在将响应作为流直接提供给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");
}

我已经只使用了单个HttpClient实例。 将调用更改为SendAsync而不是GetAsync并没有影响时间。 - Julian Silden Langlo
我觉得很难相信。ResponseHeadersReadResponseContentRead之间一定有一些可衡量的差异。 - Charlieface
我希望有明显的差异,但遗憾的是两个版本所需的时间相同。 - Julian Silden Langlo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接