由于 HttpWebRequest,.NET Core 服务在负载下停止
.NET Core service stalling under load due to HttpWebRequest
我在 Windows 服务器上有一个 ASP.NET (.NET Framework 4.8) Web 服务 运行,它使用 HttpWebRequest
发出大量传出 HTTP 请求(同步).它可以毫不费力地处理数千个并发请求。
最近,我使用更新的 HttpWebRequest
(同步)将 service/middleware 迁移到 Ubuntu 服务器上的 ASP.NET 核心(运行时 3.1)运行 .
现在这项服务在只有几百个并发请求的负载测试下停滞不前。系统journal/logs 表示健康检查(心跳)在几分钟后无法到达该服务。它开始时很好,但几分钟后它变慢并最终停止(没有响应但它不会崩溃 dotnet),然后在 5-10 分钟后再次开始工作而无需任何干预,并且每隔几次重复相同的行为分钟。
我不确定这是由于端口耗尽还是死锁造成的。如果我通过跳过所有 HttpWebRequest
调用来加载测试服务,那么它工作正常,所以我怀疑它必须与 HttpWebRequest
做一些事情导致在流量压力下出现问题。
查看 .NET Core 代码库,似乎 HttpWebRequest
(同步)为每个请求创建一个新的 HttpClient
(在我的例子中,由于参数原因,客户端未被缓存),并执行HttpClient synchronously 赞:
public override WebResponse GetResponse()
{
:
:
return SendRequest(async: false).GetAwaiter().GetResult();
:
:
}
private async Task<WebResponse> SendRequest(bool async)
{
:
:
_sendRequestTask = async ?
client.SendAsync(...) :
Task.FromResult(client.Send(...));
HttpResponseMessage responseMessage = await _sendRequestTask.ConfigureAwait(false);
:
:
}
微软官方suggestion是使用IHttpClientFactory
或SocketsHttpHandler
以获得更好的性能。我可以让我们的服务使用单例 SocketsHttpHandler
和新的 HttpClient
的每个传出请求(使用共享处理程序)来更好地重用和关闭套接字,但我主要关心的是(下面):
该服务基于同步代码,因此我将不得不同步使用异步 HttpClient
,可能使用与上面官方 .NET Core 代码相同的 method.GetAwaiter().GetResult()
技术。虽然单例 SocketsHttpHandler
可能有助于避免端口耗尽,但由于像本机 HttpWebRequest
?
这样的死锁,并发同步执行是否仍会导致停顿问题
此外,是否有一种方法(.NET Core 的另一个同步 HTTP 客户端,设置 'Connection: close' header 等)可以同步地平滑地发出大量并发 HTTP 请求,而不会出现端口耗尽或死锁,就像之前在 .NET Framework 4.8 中使用 HttpWebRequest
一样顺利?
澄清一下,所有WebRequest
相关的objects在代码中都是closed/disposed,ServicePointManager.DefaultConnectionLimit
设置为int.MaxValue
,nginx
(dotnet 的代理)已 tuned,sysctl
也已调整。
I'm not sure if this is due to port exhaustion or a deadlock.
对我来说听起来更像 thread pool exhaustion。
The service is based on synchronous code, so I'll have to use asynchronous HttpClient synchronously
为什么?
线程池耗尽的最佳解决方案是将阻塞代码重写为异步。 ASP.NET pre-Core 中的某些地方需要同步代码(例如,MVC 操作过滤器和子操作),但 ASP.NET Core 是完全异步的,包括中间件管道。
如果出于某种原因绝对不能使代码正确异步,唯一的其他解决方法是在启动时增加线程池中的最小线程数。
我在 Windows 服务器上有一个 ASP.NET (.NET Framework 4.8) Web 服务 运行,它使用 HttpWebRequest
发出大量传出 HTTP 请求(同步).它可以毫不费力地处理数千个并发请求。
最近,我使用更新的 HttpWebRequest
(同步)将 service/middleware 迁移到 Ubuntu 服务器上的 ASP.NET 核心(运行时 3.1)运行 .
现在这项服务在只有几百个并发请求的负载测试下停滞不前。系统journal/logs 表示健康检查(心跳)在几分钟后无法到达该服务。它开始时很好,但几分钟后它变慢并最终停止(没有响应但它不会崩溃 dotnet),然后在 5-10 分钟后再次开始工作而无需任何干预,并且每隔几次重复相同的行为分钟。
我不确定这是由于端口耗尽还是死锁造成的。如果我通过跳过所有 HttpWebRequest
调用来加载测试服务,那么它工作正常,所以我怀疑它必须与 HttpWebRequest
做一些事情导致在流量压力下出现问题。
查看 .NET Core 代码库,似乎 HttpWebRequest
(同步)为每个请求创建一个新的 HttpClient
(在我的例子中,由于参数原因,客户端未被缓存),并执行HttpClient synchronously 赞:
public override WebResponse GetResponse()
{
:
:
return SendRequest(async: false).GetAwaiter().GetResult();
:
:
}
private async Task<WebResponse> SendRequest(bool async)
{
:
:
_sendRequestTask = async ?
client.SendAsync(...) :
Task.FromResult(client.Send(...));
HttpResponseMessage responseMessage = await _sendRequestTask.ConfigureAwait(false);
:
:
}
微软官方suggestion是使用IHttpClientFactory
或SocketsHttpHandler
以获得更好的性能。我可以让我们的服务使用单例 SocketsHttpHandler
和新的 HttpClient
的每个传出请求(使用共享处理程序)来更好地重用和关闭套接字,但我主要关心的是(下面):
该服务基于同步代码,因此我将不得不同步使用异步 HttpClient
,可能使用与上面官方 .NET Core 代码相同的 method.GetAwaiter().GetResult()
技术。虽然单例 SocketsHttpHandler
可能有助于避免端口耗尽,但由于像本机 HttpWebRequest
?
此外,是否有一种方法(.NET Core 的另一个同步 HTTP 客户端,设置 'Connection: close' header 等)可以同步地平滑地发出大量并发 HTTP 请求,而不会出现端口耗尽或死锁,就像之前在 .NET Framework 4.8 中使用 HttpWebRequest
一样顺利?
澄清一下,所有WebRequest
相关的objects在代码中都是closed/disposed,ServicePointManager.DefaultConnectionLimit
设置为int.MaxValue
,nginx
(dotnet 的代理)已 tuned,sysctl
也已调整。
I'm not sure if this is due to port exhaustion or a deadlock.
对我来说听起来更像 thread pool exhaustion。
The service is based on synchronous code, so I'll have to use asynchronous HttpClient synchronously
为什么?
线程池耗尽的最佳解决方案是将阻塞代码重写为异步。 ASP.NET pre-Core 中的某些地方需要同步代码(例如,MVC 操作过滤器和子操作),但 ASP.NET Core 是完全异步的,包括中间件管道。
如果出于某种原因绝对不能使代码正确异步,唯一的其他解决方法是在启动时增加线程池中的最小线程数。