延迟时间增加 - C# ASP.NET Core Web API

Increased latency by time - C# ASP.NET Core Web API

我有一个 ASP.NET Core 3.1 Web API 服务托管在 Windows Server 2016 (IIS) 上。这是一个非常简单的 Web 服务,可将所有传入请求路由到特定端点。

代码如下:

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;

namespace Proxy
{
    public class ProxyMiddleware
    {
        private readonly RequestDelegate next;
        private readonly string destinationPath;
        private readonly IProxyHttpClient client;

        public ProxyMiddleware(RequestDelegate next, IProxyHttpClient client)
        {
            this.next = next;
            this.client = client;
            this.destinationPath = "otherservice.svc";
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                if (!context.Request.Path.ToString().ToLower().Contains(this.destinationPath))
                {
                    await next.Invoke(context);
                    return;
                }

                HttpResponseMessage response = await this.client.SendRequest(context.Request);
                context.Response.StatusCode = (int) response.StatusCode;

                await context.CopyProxyHttpResponse(response);
            }
            catch (HttpRequestException ex)
            {
                await context.Response.WriteAsync(ex.Message);
            }
            catch (Exception ex)
            {
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
                await context.Response.WriteAsync(ex.ToString());
            }
        }
    }
}

IProxyClient:

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace Proxy
{
    public class ProxyHttpClient : IProxyHttpClient
    {
        private readonly string otherServiceUrl = "https://172.21.22.3:443";
        private readonly HttpClient httpClient;

        public ProxyHttpClient()
        {
            var handler = new HttpClientHandler
            {
                ClientCertificateOptions = ClientCertificateOption.Manual,
                ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true
            };
            this.httpClient = new HttpClient(handler);
        }

        public async Task<HttpResponseMessage> SendRequest(HttpRequest httpRequest)
        {
            HttpRequestMessage requestMessage = httpRequest.CreateProxyHttpRequest(new Uri(this.otherServiceUrl + httpRequest.Path + httpRequest.QueryString));

            return await this.httpClient.SendAsync(requestMessage);
        }
    }
}

分机:

using System;
using System.Net.Http;
using Microsoft.AspNetCore.Http;

namespace Proxy
{
    public static class HttpRequestExtensions
    {
        // obtained from https://github.com/aspnet/Proxy/blob/master/src/Microsoft.AspNetCore.Proxy/ProxyAdvancedExtensions.cs
        public static HttpRequestMessage CreateProxyHttpRequest(this HttpRequest request, Uri uri)
        {
            var requestMessage = new HttpRequestMessage();
            var requestMethod = request.Method;
            if (!HttpMethods.IsGet(requestMethod) &&
                !HttpMethods.IsHead(requestMethod) &&
                !HttpMethods.IsDelete(requestMethod) &&
                !HttpMethods.IsTrace(requestMethod))
            {
                var streamContent = new StreamContent(request.Body);
                requestMessage.Content = streamContent;
            }

            // Copy the request headers
            foreach (var header in request.Headers)
            {
                if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
                {
                    requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                }
            }

            requestMessage.Headers.Host = uri.Authority;
            requestMessage.RequestUri = uri;
            requestMessage.Method = new HttpMethod(request.Method);

            return requestMessage;
        }

    }
}

.proj:

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
        <AspNetCoreModuleName>AspNetCoreModuleV2</AspNetCoreModuleName>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.22" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
        <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
    </ItemGroup>

</Project>

如前所述,我在 windows 服务器 2016 上的 IIS 上部署了此服务。部署后,它运行良好,持续运行 6-8 小时,并且随着它持续运行,延迟会随着时间的推移而增加.它不断增加,直到达到有效下降的程度。例如。请求需要 3 分钟以上才能得到回复(这实际上变成了超时)。无论我是从服务器内部 (localhost) 还是通过它的 public IP 从网络外部调用代理服务,这种行为都是一致的。

一旦我重新启动 服务器,一切都会恢复正常!它保持健康 6-8 小时,同样的行为再次发生。我什至尝试在 IIS 上重新启动服务本身或回收应用程序池 -> 不起作用。必须是服务器(机器)本身。

我们检查了超时增加时服务器上的资源利用率,一切正常 - 内存利用率约为 50%(4GB 中的 2GB)并且 CPU 几乎没有使用。

来自 Yarp 文档:

Middleware should avoid interacting with the request or response bodies. Bodies are not buffered by default, so interacting with them can prevent them from reaching their destinations. While enabling buffering is possible, it's discouraged as it can add significant memory and latency overhead. Using a wrapped, streaming approach is recommended if the body must be examined or modified. See the ResponseCompression middleware for an example.

我建议使用此库而不是自定义解决方案。 https://microsoft.github.io/reverse-proxy/articles/getting-started.html