[英]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.