C# .NET核心服务因HttpWebRequest而在负载下暂停
我在Windows服务器上运行了一个ASP.NET(.NET Framework 4.8)web服务,它使用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)在几分钟后无法到达服务。它一开始运行正常,但几分钟后
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
以获得更好的性能。我可以让我们的服务在每个传出请求(使用共享处理程序)中使用singletonSocketsHttpHandler
和newHttpClient
,以便更好地重用和关闭套接字,但我主要关心的是(以下):
该服务基于同步代码,因此我必须同步使用asynchronousHttpClient
,可能使用与上述官方.NET核心代码相同的方法.GetAwaiter().GetResult()
技术。虽然singletonSocketsHttpHandler
可能有助于避免端口耗尽,但并发同步执行是否仍会由于本机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上运行高负载服务器的经验不多。我想说的是,从类似产品的默认设置开始,然后进行压力测试,必要时进行调整。