2015-05-24 67 views
1

所以我創建它使用一個HTTP API來獲取每個大約〜50000帳戶的狀態的應用程序。代碼循環遍歷每個帳戶,併爲其發送HTTP請求。當我收到http請求的響應時,根據一些標準,我創建一個新線程來管理該帳戶。瓶頸3000+線程.NET應用程序(HttpWebReqsuest.BeginGetResponse)

現在通常,我可以很容易地發送出大約每秒1K的請求,但一旦我打〜3100的活動線程,http請求循環開始減速和冷凍每秒只有約1請求。然後它突然跳回到每秒3k,然後凍結幾秒鐘......等等。重要的是它看起來不像是逐漸退化。它發生得非常突然。

很明顯,某處存在瓶頸,但我不確定在哪裏。我已確定TCP參數(可用端口的最大數量)設置爲高限。我有servicepoint.defaultconnection限制設置爲int.maxvalue。

我的CPU是帶有專用1Gbps的4核(帶HT的8線程)。我正在考慮轉向更大的(32核心2x CPU)機器。但我不確定它是否會有任何好處。我想知道是否有人知道我可能遇到的其他瓶頸。

我甚至接近用盡我的全部帶寬或存儲,我知道這是不是有問題。

這大致就是我的代碼看起來像

Sub Main() 
    While 1 
     For each account As Account in GetAccountsFromDatabase()'~50K Accounts 
      dim request = HttpWebRequest.Create("http://api.com?id=" & account.name) 
      request.BeginGetResponse(New AsyncCallback(AddressOf HandleResponse), request) 
      RequestsSent += 1 
      Console.Writeline("Request") 

      'After ~3000 active threads in the process, this loop begins freezing/slowing down. 

      if RequestsSent > 5000 then 'Limit 
       Thread.Sleep(5000) 
       RequestsSent = 0 
      end if 

     Next 
    End While 
End Sub 

Sub HandleResponse(ByVal asynchronousResult As IAsyncResult) 
    Dim webRequest As HttpWebRequest = DirectCast(asynchronousResult.AsyncState, HttpWebRequest) 
    Dim webResponse As HttpWebResponse = webRequest.EndGetResponse(asynchronousResult) 
    Dim stream As New StreamReader(webResponse.GetResponseStream()) 
    Dim response = stream.ReadToEnd 

    if response.contains("somestuff") then  
     dim t As New Thread(AddressOf ProcessAccount) 
     t.Start(account) 
    end if 

End Sub 

Sub ProcessAccount(acc As Account) 
    'Process the account. Involves some other loops, http requests...etc 
End Sub 
+0

你用什麼調試工具?你確定內核是100%嗎?我從來沒有使用過,但[Visual Studio Concurrency Visualizer](https://msdn.microsoft.com/en-us/library/ee329530.aspx)看起來很有前途。 – JDong

+0

從來沒有聽說過,我現在會檢查出來。 –

+0

3000線程?必須有辦法減少這種情況。 –

回答

3

嗯,你是異步啓動請求,但一旦BeginGetResponse完成您同步處理該請求。這很可能會最終消耗池中線程的的。也許配置中的池限制大約是3000個線程。

你需要把它扔掉。一旦你進入了數百個線程,並且肯定在1000年,你需要切換到異步非阻塞IO。隨着await這已變得相當容易。

使流讀出部分異步(ReadToEnd)。很可能,您應該刪除所有代碼並將其替換爲var str = await new HttpClient().GetAsync(url);(C#)。

接下來,運行50,000(!)線程是不明智的。僅此一項就可以讓你重新獲得50GB(!)的內存堆棧。操作系統開始有麻煩調度所有這些線程以及我在測試過程中發現的問題。例如,雖然鼠標驅動程序具有非常高的優先級,但鼠標停止移動數秒。顯然,內核團隊並不在乎這種情況。

可能您應該按給定的並行度處理帳戶。例如,同時只處理100個。

如果您需要在同一時間由於某種原因,處理它們則ProcessAccount必須是異步爲好。別無退路。

+0

我嘗試用您的「等待」建議替換所有請求代碼,但最終導致問題變得更糟。循環將在〜1K線程處開始​​掛起/凍結。 –

+0

然後你沒有做足夠的異步的東西。發生掛起時暫停調試器。有多少個線程?你如何看待你應該像100並行處理的建議? – usr

0

這裏有一些僞代碼來解釋我將如何處理這個問題。你絕對不想放棄無限數量的線程。工作線程的數量應該根據您的環境進行調整。

WorkQueue = GetAccountsFromDatabase()'~50K Accounts 

for 1 to 100 
    Workers.Add(Task.Run(() => Worker(WorkQueue))) 
end 

WaitForWorkersToFinish(Workers) 

return 

Worker(WorkQueue) 
{ 
    while 1 
     lock (WorkQueue) 
      WorkItem = WorkQueue.Next() // removes item 
     end 

     if WorkItem == null 
      return // out of work 
     end 

     ProcessWorkItem(WorkItem) 
    end 
}