执行超过 1000 个 HTTP 请求任务失败
Executing more than 1000 HTTP request tasks fails
我正在尝试对 API 进行负载测试。我同时执行一个任务,每个任务执行一个 HTTP 请求。我使用 Task.WhenAll(mytasks)
等待所有任务完成。请求如下所示:
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
using (var jsonResponse = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
var jsonSerializer = new DataContractJsonSerializer(typeof(Borders));
var borders = (Borders)jsonSerializer.ReadObject(jsonResponse);
return borders;
}
}
这至少适用于数千个任务。但是,如果我开始的任务超过几千个,我会 运行 进入 HttpRequestExceptions
:
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a receive. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
--- End of inner exception stack trace ---
at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar)
--- End of inner exception stack trace ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
at BusinessLogic.<GetBorder>d__6d.MoveNext() in c:\BusinessLogic.cs:line 757
所以我的问题是:为什么会发生这种情况(超过 1000 个任务)?我怎样才能防止这种情况发生?我显然可以将我的任务块分成 <1000 个块,但我想把它留给底层系统...
I would like to leave this to the underlying system...
这不是个好主意。 .NET Framework 在确定 IO 的最佳并行度方面的能力为零。
并行发出那么多请求通常不是一个好主意,因为此处强调的资源可能在此之前就已经用完了。显然,您的后端服务器无法处理这种程度的并行性。强行根据消息给你挂断
仅仅因为我们现在有简单的异步 IO await
并不意味着您可以使用 1000 个并行请求向您的资源发送垃圾邮件。
使用一种常见的解决方案来执行一系列具有一定并行度的异步操作。我也喜欢http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx but there are solutions based on ActionBlock
我之前写过这个要点
//PM> Install-Package Rx-Linq
readonly List<string> _list = new List<string> { "http://www.google.com", "https://www.gmail.com", "http://www.aripaev.ee" };
private readonly string format = "[{0}] {1} : {2} [{3}]";
[Category("WebClient")]
[TestCase("sync" )]
public void SynchronousTest(string name)
{
DateTime start = DateTime.Now;
var dict = _list.ToDictionary(o => o, o => new WebClient().DownloadString(new Uri(o)));
dict.Keys.ToList().ForEach(o => Console.WriteLine(format, DateTime.Now - start, o, dict[o].Length, name));
}
[Category("WebClient")]
[TestCase("async")]
public void AsynchronousTest(string name)
{
DateTime start = DateTime.Now;
var dict = _list.ToDictionary(o => o, async o => await new WebClient().DownloadStringTaskAsync(new Uri(o)));
dict.Keys.ToList().ForEach(o => Console.WriteLine(format, DateTime.Now - start, o, dict[o].Result.Length, name));
}
[Category("WebClient")]
[TestCase("lazy")]
public void LazyTest(string name)
{
var start = DateTime.Now;
var dict = _list.ToDictionary(o => o, o => new Lazy<string>(() => new WebClient().DownloadString(new Uri(o))));
dict.Keys.ToList().ForEach(o => Console.WriteLine(format, DateTime.Now - start, o, dict[o].Value.Length, name));
}
结果如下:
[00:00:00.9520952] http://www.google.com : 51348 [sync]
[00:00:00.9520952] https://www.gmail.com : 58704 [sync]
[00:00:00.9520952] http://www.aripaev.ee : 208324 [sync]
[00:00:00.0010001] http://www.google.com : 51412 [lazy]
[00:00:00.6550655] https://www.gmail.com : 58664 [lazy]
[00:00:00.7690769] http://www.aripaev.ee : 208324 [lazy]
[00:00:00.1430143] http://www.google.com : 51366 [async]
[00:00:00.3430343] https://www.gmail.com : 58616 [async]
[00:00:00.5150515] http://www.aripaev.ee : 208324 [async]
单个 machine/server 可以同时处理 300-500 个请求,但即使这样也是对 system/network 资源的压力测试。
正如 usr 在他的回答中所述,您的服务器正在关闭连接,导致错误消息:
An existing connection was forcibly closed by the remote host.
你还不知道你的瓶颈到底是什么。可能是您的服务器无法处理请求,可能是服务器由于某些速率限制功能而阻止了它们,也可能是压力客户端本身,因为无法足够快地处理响应,因此保留了太多响应连接打开。您需要收集更多信息才能优化您的服务。
与其尝试编写自己的压力测试工具,不如使用成熟的工具,例如 Apache JMeter。您应该能够构建类似于您想要的测试用例的测试计划 运行。
在某些时候,您将达到单个机器无法模拟的用户数。有一些服务(比如 Redline13) that let you run your tests from EC2 instances and provide you with the tools to analyze the resulting data. You could also use this JMeter EC2 script 从多台机器上执行你的测试计划。
我正在尝试对 API 进行负载测试。我同时执行一个任务,每个任务执行一个 HTTP 请求。我使用 Task.WhenAll(mytasks)
等待所有任务完成。请求如下所示:
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
using (var jsonResponse = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
var jsonSerializer = new DataContractJsonSerializer(typeof(Borders));
var borders = (Borders)jsonSerializer.ReadObject(jsonResponse);
return borders;
}
}
这至少适用于数千个任务。但是,如果我开始的任务超过几千个,我会 运行 进入 HttpRequestExceptions
:
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a receive. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
--- End of inner exception stack trace ---
at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar)
--- End of inner exception stack trace ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
at BusinessLogic.<GetBorder>d__6d.MoveNext() in c:\BusinessLogic.cs:line 757
所以我的问题是:为什么会发生这种情况(超过 1000 个任务)?我怎样才能防止这种情况发生?我显然可以将我的任务块分成 <1000 个块,但我想把它留给底层系统...
I would like to leave this to the underlying system...
这不是个好主意。 .NET Framework 在确定 IO 的最佳并行度方面的能力为零。
并行发出那么多请求通常不是一个好主意,因为此处强调的资源可能在此之前就已经用完了。显然,您的后端服务器无法处理这种程度的并行性。强行根据消息给你挂断
仅仅因为我们现在有简单的异步 IO await
并不意味着您可以使用 1000 个并行请求向您的资源发送垃圾邮件。
使用一种常见的解决方案来执行一系列具有一定并行度的异步操作。我也喜欢http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx but there are solutions based on ActionBlock
我之前写过这个要点
//PM> Install-Package Rx-Linq
readonly List<string> _list = new List<string> { "http://www.google.com", "https://www.gmail.com", "http://www.aripaev.ee" };
private readonly string format = "[{0}] {1} : {2} [{3}]";
[Category("WebClient")]
[TestCase("sync" )]
public void SynchronousTest(string name)
{
DateTime start = DateTime.Now;
var dict = _list.ToDictionary(o => o, o => new WebClient().DownloadString(new Uri(o)));
dict.Keys.ToList().ForEach(o => Console.WriteLine(format, DateTime.Now - start, o, dict[o].Length, name));
}
[Category("WebClient")]
[TestCase("async")]
public void AsynchronousTest(string name)
{
DateTime start = DateTime.Now;
var dict = _list.ToDictionary(o => o, async o => await new WebClient().DownloadStringTaskAsync(new Uri(o)));
dict.Keys.ToList().ForEach(o => Console.WriteLine(format, DateTime.Now - start, o, dict[o].Result.Length, name));
}
[Category("WebClient")]
[TestCase("lazy")]
public void LazyTest(string name)
{
var start = DateTime.Now;
var dict = _list.ToDictionary(o => o, o => new Lazy<string>(() => new WebClient().DownloadString(new Uri(o))));
dict.Keys.ToList().ForEach(o => Console.WriteLine(format, DateTime.Now - start, o, dict[o].Value.Length, name));
}
结果如下:
[00:00:00.9520952] http://www.google.com : 51348 [sync]
[00:00:00.9520952] https://www.gmail.com : 58704 [sync]
[00:00:00.9520952] http://www.aripaev.ee : 208324 [sync]
[00:00:00.0010001] http://www.google.com : 51412 [lazy]
[00:00:00.6550655] https://www.gmail.com : 58664 [lazy]
[00:00:00.7690769] http://www.aripaev.ee : 208324 [lazy]
[00:00:00.1430143] http://www.google.com : 51366 [async]
[00:00:00.3430343] https://www.gmail.com : 58616 [async]
[00:00:00.5150515] http://www.aripaev.ee : 208324 [async]
单个 machine/server 可以同时处理 300-500 个请求,但即使这样也是对 system/network 资源的压力测试。
正如 usr 在他的回答中所述,您的服务器正在关闭连接,导致错误消息:
An existing connection was forcibly closed by the remote host.
你还不知道你的瓶颈到底是什么。可能是您的服务器无法处理请求,可能是服务器由于某些速率限制功能而阻止了它们,也可能是压力客户端本身,因为无法足够快地处理响应,因此保留了太多响应连接打开。您需要收集更多信息才能优化您的服务。
与其尝试编写自己的压力测试工具,不如使用成熟的工具,例如 Apache JMeter。您应该能够构建类似于您想要的测试用例的测试计划 运行。
在某些时候,您将达到单个机器无法模拟的用户数。有一些服务(比如 Redline13) that let you run your tests from EC2 instances and provide you with the tools to analyze the resulting data. You could also use this JMeter EC2 script 从多台机器上执行你的测试计划。