C# .NET核心服务因HttpWebRequest而在负载下暂停

C# .NET核心服务因HttpWebRequest而在负载下暂停,c#,asp.net-core,httpwebrequest,dotnet-httpclient,C#,Asp.net Core,Httpwebrequest,Dotnet Httpclient,我在Windows服务器上运行了一个ASP.NET(.NET Framework 4.8)web服务,它使用HttpWebRequest(同步)发出大量传出HTTP请求。它可以毫无困难地处理数千个并发请求 最近,我使用更新的HttpWebRequest(同步)将服务/中间件迁移到运行在Ubuntu服务器上的ASP.NET核心(运行时3.1) 现在,这个服务在负载测试下暂停,只有几百个并发请求。系统日志/日志表明运行状况检查(heartbeat)在几分钟后无法到达服务。它一开始运行正常,但几分钟后

我在Windows服务器上运行了一个ASP.NET(.NET Framework 4.8)web服务,它使用
HttpWebRequest
(同步)发出大量传出HTTP请求。它可以毫无困难地处理数千个并发请求

最近,我使用更新的
HttpWebRequest
(同步)将服务/中间件迁移到运行在Ubuntu服务器上的ASP.NET核心(运行时3.1)

现在,这个服务在负载测试下暂停,只有几百个并发请求。系统日志/日志表明运行状况检查(heartbeat)在几分钟后无法到达服务。它一开始运行正常,但几分钟后速度变慢并最终停止(没有响应,但不会使dotnet崩溃),然后在5-10分钟后重新开始工作,无需任何干预,每隔几分钟重复一次相同的行为

我不确定这是由于端口耗尽还是死锁。如果我通过跳过所有
HttpWebRequest
调用来加载测试服务,那么它工作正常,因此我怀疑它与
HttpWebRequest
有关,从而在流量压力下导致问题

查看.NET核心代码库,似乎
HttpWebRequest
(同步)为每个请求创建了一个新的
HttpClient
(在我的例子中,由于参数的原因,客户端未被缓存),并执行如下操作:

public覆盖WebResponse GetResponse()
{
:
:
返回SendRequest(异步:false).GetAwaiter().GetResult();
:
:
}
专用异步任务SendRequest(bool异步)
{
:
:
_sendRequestTask=async?
client.SendAsync(…):
Task.FromResult(client.Send(…);
HttpResponseMessage responseMessage=await _sendRequestTask.ConfigureAwait(false);
:
:
}
微软官方将使用
IHttpClientFactory
SocketsHttpHandler
以获得更好的性能。我可以让我们的服务在每个传出请求(使用共享处理程序)中使用singleton
SocketsHttpHandler
和new
HttpClient
,以便更好地重用和关闭套接字,但我主要关心的是(以下):

该服务基于同步代码,因此我必须同步使用asynchronous
HttpClient
,可能使用与上述官方.NET核心代码相同的
方法.GetAwaiter().GetResult()
技术。虽然singleton
SocketsHttpHandler
可能有助于避免端口耗尽,但并发同步执行是否仍会由于本机
HttpWebRequest
之类的死锁而导致暂停问题

此外,是否有一种方法(另一种用于.NET Core的同步HTTP客户端,设置“Connection:close”头等)可以平滑地同步发出大量并发HTTP请求,而不会出现端口耗尽或死锁,就像它在.NET Framework 4.8中的
HttpWebRequest
之前工作时一样

为了澄清,所有
WebRequest
相关对象都已在代码中正确关闭/处理,
ServicePointManager.DefaultConnectionLimit
设置为
int.MaxValue
nginx
(dotnet代理)已关闭,
sysctl
也已调整

我不确定这是由于端口耗尽还是死锁

听起来更像我

该服务基于同步代码,因此我必须同步使用异步HttpClient

为什么?

线程池耗尽的最佳解决方案是将阻塞代码重写为异步。ASP.NET预核心中有些地方需要同步代码(例如,MVC操作过滤器和子操作),但ASP.NET核心是完全异步的,包括中间件管道


如果由于某种原因,您完全无法使代码正确地异步,那么唯一的其他解决方法是在启动时增加线程池中的最小线程数。

感谢您的回复。我同意你的建议,异步是最好的解决方案,它在我的待办事项清单上,但重构和测试需要几周的时间,所以我试图找出原因并找到解决这个问题的临时方法。到目前为止,我发现
ThreadPool
大约有32743个可用的工作线程(总共32767个)和所有1000个可用的异步线程,就在问题开始之前,当建立的入站连接超过1000个左右时(根据
netstat
),而出站保持在50以下,因此,它可能与HttpWebRequest无关。我还在挠头。嗨,斯蒂芬,我认为这确实是一个线程池耗尽的问题。我在重载下进行了一次进程转储(使用
dotnet trace
),对其进行了分析(使用
PrefView
),并注意到在
Microsoft Windows DotNETRuntime/ThreadPoolWorkerThreadAdjustment/Adjustment
下发生了一系列事件,新的工作线程计数上升到90,调整原因是
饥饿。我将把同步/阻塞操作更改为异步。我仍然不明白为什么它能在.NETFramework4中工作(没有线程耗尽),以及.NETCore中发生了什么变化来解决这个问题?你有什么想法吗?谢谢你。我不知道细节,但是在将东西放入线程池的时间方面,.NETFramework和.NETCore之间有一些变化。也有可能是操作系统的不同;Windows驱动程序被迫是异步的,但Linux允许同步驱动程序。好吧,异步代码和高负载(每秒几百个请求,短脉冲),我还是应该将
ThreadPool.SetMinThreads
设置为安全的,还是仅仅依赖
ThreadPool
的线程管理算法?@Nick:我在Linux上运行高负载服务器的经验不多。我想说的是,从类似产品的默认设置开始,然后进行压力测试,必要时进行调整。