[英]Strange behavior with HttpWebRequest in C# console application
我有一个简单的控制台应用程序,该应用程序将许多POST请求发送到服务器,并打印出每秒成功的平均请求数。 该应用程序利用HttpWebRequest类进行实际的消息发送。 它是多线程的,并利用C#ThreadPool。
我已经在服务器上测试了该应用程序,该服务器可以立即发送回响应,并且该服务器实际上可以完成一些工作。 在前一种情况下,我的应用程序的单个实例每秒能够实现约30k消息。 在后面的场景中,每秒大约有12k条消息。
我观察到的奇怪行为是,如果我运行应用程序的多个实例(2-4个),则比起运行单个实例,可以获得更大的累积吞吐量。 因此,对于上述两种情况,我分别达到40k和20k的累积吞吐量。
我无法弄清楚的是,如果事实证明我的应用程序的单个实例在第一种情况下可达到30k的吞吐量,为什么在第二种情况下我的应用程序的单个实例无法达到20k的吞吐量。
我玩过maxconnection参数以及各种线程池参数,例如minthreads / maxthreads。 我也尝试过更改实现,例如Parallel.For / Task.Run / ThreadPool.QueueUserWorkItem。 我什至已经测试了异步范例。 不幸的是,所有版本都表现出相同的行为。
知道会发生什么吗?
编辑
主循环如下所示:
Task.Run(() =>
{
while (true)
{
_throttle.WaitOne();
ThreadPool.QueueUserWorkItem(SendStressMessage);
}
});
剥去SendStressMessage不必要的部分,如下所示:
private static void SendStressMessage(Object state)
{
var message = _sampleMessages.ElementAt(_random.Next(_sampleMessages.Count));
if (SendMessage(_stressuri, message))
{
Interlocked.Increment(ref _successfulMessages);
_sucessfulMessageCounter.Increment();
_QPSCounter.Increment();
}
else
{
Interlocked.Increment(ref _failedMessages);
_failedMessageCounter.Increment();
}
// Check time of last QPS read out
if (DateTime.UtcNow.Subtract(_lastPrint).TotalSeconds > _printDelay)
{
lock (_lock)
{
// Check one last time while holding the lock
if (DateTime.UtcNow.Subtract(_lastPrint).TotalSeconds > _printDelay)
{
// Print current QPS and update last print / successful message count
Console.WriteLine("Current QPS: " + (int)((Thread.VolatileRead(ref _successfulMessages) - _lastSuccessfulMessages) / DateTime.UtcNow.Subtract(_lastPrint).TotalSeconds));
_lastSuccessfulMessages = _successfulMessages;
_lastPrint = DateTime.UtcNow;
}
}
}
_throttle.Release();
}
最后,sendmessage方法如下所示:
private static bool SendMessage(Uri uri, byte[] message)
{
HttpWebRequest request = null;
HttpWebResponse response = null;
try
{
request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.KeepAlive = true;
request.Proxy = null;
request.ContentLength = message.Length;
// Write to request body
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(message, 0, message.Length);
}
// Try posting message
response = (HttpWebResponse)request.GetResponse();
// Check response status and increment accordingly
if (response.StatusCode == HttpStatusCode.OK)
{
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
finally
{
// Dispose of response
if (response != null)
{
response.Close();
response.Dispose();
}
}
}
如果您在上面注意到,那儿有一把锁。 我尝试删除锁定,但这不会影响性能。
更新
如上所述,当我将应用程序重写为异步时,最初遇到的性能很差。 我没有意识到实现是不正确的,因为我实际上在阻塞IO调用。 正如@David d C e Freitas指出的那样,我没有使用GetRequestStream的异步版本。 最后,我的初始实现并没有限制待处理消息的数量。 由于创建了太多异步句柄,因此大大降低了性能。
没有上述问题的最终解决方案的概要如下:
主循环:
Task.Run(async () =>
{
while (true)
{
await _throttle.WaitAsync();
SendStressMessageAsync();
}
});
以下是SendStressMessageAsync()的精简版本:
private static async void SendStressMessageAsync()
{
var message = _sampleMessages.ElementAt(_random.Next(_sampleMessages.Count));
if (await SendMessageAsync(_stressuri, message))
{
Interlocked.Increment(ref _successfulMessages);
_sucessfulMessageCounter.Increment();
_QPSCounter.Increment();
}
else
{
//Failed request - increment failed count
Interlocked.Increment(ref _failedMessages);
_failedMessageCounter.Increment();
}
// Check time of last QPS read out
if (DateTime.UtcNow.Subtract(_lastPrint).TotalSeconds > _printDelay)
{
await _printlock.WaitAsync();
// Check one last time while holding the lock
if (DateTime.UtcNow.Subtract(_lastPrint).TotalSeconds > _printDelay)
{
// Print current QPS and update last print / successful message count
Console.WriteLine("Current QPS: " + (int)(Interlocked.Read(ref _successfulMessages) / DateTime.UtcNow.Subtract(_startTime).TotalSeconds));
_lastPrint = DateTime.UtcNow;
}
_printlock.Release();
}
_throttle.Release();
}
最后,异步发送消息如下所示:
private static async Task<bool> SendMessageAsync(Uri uri, byte[] message)
{
HttpWebRequest request = null;
HttpWebResponse response = null;
try
{
// Create POST request to provided VIP / Port
request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.KeepAlive = true;
request.Proxy = null;
request.ContentLength = message.Length;
// Write to request body
using (Stream requestStream = await request.GetRequestStreamAsync())
{
requestStream.Write(message, 0, message.Length);
}
// Try posting message
response = (HttpWebResponse)await request.GetResponseAsync();
// Check response status and increment accordingly
if (response.StatusCode == HttpStatusCode.OK)
{
return true;
}
else
{
return false;
}
}
catch
{
//Failed request - increment failed count
return false;
}
finally
{
// Dispose of response
if (response != null)
{
response.Close();
response.Dispose();
}
}
}
我最好的猜测是,您只是在运行多个实例之前没有充分利用资源。 使用并行或异步并不能神奇地加快您的应用程序的速度,您需要将它们放置在存在瓶颈的正确位置。
特定的问题是您可能使用了并行,但是您需要更大程度的并行性,因为操作的本质不是CPU密集型的,而是I / O密集型的。 您可以同时使用并行和异步来充分利用您的资源。
这就是我不需要任何特定代码即可完成的全部工作。
.NET框架使用应用程序配置项maxconnections限制与特定主机的连接数。 默认情况下,此限制是每个主机2个连接。
这意味着在默认设置下,任何时候都只能激活2个请求。 如果您一次启动10个请求,则其中只有2个将立即连接。 其余的将旋转,直到可用插槽之一可用。
尝试修改您的App.config
以包括以下内容:
<configuration>
<system.net>
<connectionManagement>
<add address = "*" maxconnection = "10" />
</connectionManagement>
</system.net>
</configuration>
这将允许每个主机10个连接,这将大大提高吞吐量。
您的问题似乎是:为什么什么都不做的请求返回到30kmsg / s的速度,而有些工作的请求返回到12kmsg / s的速度呢?
您可以在测试应用程序上看到有多少个请求处于等待状态(进行中)吗? 即有多少人打开了套接字并完全写出了请求,但是还没有开始接收一些数据。 也许您的测试应用程序一次只能处理如此多的“正在进行中”的请求,然后才减慢挂起的队列的速度。
所以这个问题是关于服务器而不是您的测试客户端应用程序的,因为服务器决定了它每秒比测试应用程序更有可能处理多少个请求。 服务器写的是什么,您在那里做了什么调整?
更新:
要考虑的一件事是,在您未处理的幕后发生了许多同步操作:
阻止任何这些操作将限制您在单线程实例中的吞吐量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.