簡體   English   中英

高性能C#服務器套接字的提示/技巧

[英]Tips / techniques for high-performance C# server sockets

我有一個.NET 2.0服務器似乎遇到了擴展問題,可能是由於套接字處理代碼設計不佳,我正在尋找有關如何重新設計它以提高性能的指導。

使用場景: 50到150個客戶端,每個客戶端的高速率(高達100秒/秒)的小消息(每個10字節)。 客戶端連接是長期的 - 通常是幾個小時。 (服務器是交易系統的一部分。客戶端消息被聚合成組以通過較少數量的“出站”套接字連接發送到交換機,並且當交換機處理每個組時,確認消息被發送回客戶端。 。)OS是Windows Server 2003,硬件是2 x 4核X5355。

當前客戶端套接字設計: TcpListener生成一個線程,以在客戶端連接時讀取每個客戶端套接字。 線程在Socket.ReceiveSocket.Receive ,解析傳入的消息並將它們插入到一組隊列中以供核心服務器邏輯處理。 使用來自與交換機側通信的線程的異步Socket.BeginSend調用,通過客戶端套接字發回確認消息。

觀察到的問題:隨着客戶端數量的增加(現在為60-70),我們開始在向客戶端發送數據和從客戶端接收數據時看到間歇性延遲高達100毫秒。 (我們記錄每條確認消息的時間戳,我們可以看到時間戳序列中偶爾存在長時間間隔,這些間隙來自同一組中通常在幾毫秒內完成的一組ack。)

整體系統CPU使用率很低(<10%),有足夠的空閑RAM,核心邏輯和出站(面向交換)端表現良好,因此問題似乎與面向客戶端的套接字代碼隔離開來。 服務器和客戶端(千兆局域網)之間有足夠的網絡帶寬,我們排除了網絡或硬件層問題。

任何有用資源的建議或指示都將不勝感激。 如果有人有任何診斷或調試技巧可以確定出錯的地方,那么這些技巧也會很棒。

注意:我有MSDN雜志的文章Winsock:在.NET中使用高性能套接字更接近線路,我已經瀏覽了Kodart“XF.Server”組件 - 它看起來很粗略。

.NET 3.5環境中的套接字I / O性能得到了改進。 您可以使用ReceiveAsync / SendAsync而不是BeginReceive / BeginSend來獲得更好的性能。 把它拿出來:

http://msdn.microsoft.com/en-us/library/bb968780.aspx

其中很多都與系統上運行的許多線程有關,內核為每個線程提供了時間片。 設計簡單,但不能很好地擴展。

你可能應該看一下使用Socket.BeginReceive,它將在.net線程池上執行(你可以用某種方式指定它使用的線程數),然后從異步回調中推送到一個隊列(可以在任何一個中運行) .NET線程)。 這應該會給你更高的性能。

每個客戶端的一個線程看起來非常過分,特別是考慮到這里的總體CPU使用率較低。 通常,您需要一個小的線程池來為所有客戶端提供服務,使用BeginReceive等待工作異步 - 然后簡單地將處理發送給其中一個工作者(可能只需將工作添加到同步隊列中,所有工作人員都在等待)。

我不是一個C#家伙,但對於高性能套接字服務器,最具可擴展性的解決方案是使用I / O完成端口,其中有許多活動線程適合於進程運行的CPU,而不是使用每個連接一個線程的模型。

在您的情況下,使用8核機器,您將需要16個總線程,其中8個並發運行。 (其他8個基本保留。)

正如其他人所建議的那樣,實現這一點的最佳方法是使面向客戶端的代碼全部異步。 在TcpServer()上使用BeginAccept(),這樣就不必手動生成線程。 然后在從接受的TcpClient獲取的基礎網絡流上使用BeginRead()/ BeginWrite()。

但是,有一件事我不明白。 你說這些是長期存在的連接,以及大量的客戶端。 假設系統已達到穩定狀態,您的最大客戶端(例如70)已連接。 您有70個線程偵聽客戶端數據包。 然后,系統仍然應該響應。 除非您的應用程序有內存/處理泄漏,並且您的資源不足以使您的服務器正在分頁。 我會在調用Accept()的過程中放置​​一個計時器,在那里啟動客戶端線程並查看需要多長時間。 此外,我將啟動taskmanager和PerfMon,並監控應用程序的“非頁面緩沖池”,“虛擬內存”,“處理計數”,並查看該應用程序是否處於資源緊張狀態。

雖然確實去Async是正確的方法,但我不相信它是否能真正解決潛在的問題。 我會按照我的建議監視應用程序,並確保沒有泄漏內存和句柄的內在問題。 在這方面,上面的“BigBlackMan”是對的 - 你需要更多的儀器才能繼續。 不知道為什么他被投票了。

隨機間歇〜250毫秒的延遲可能是由於TCP使用的Nagle算法造成的。 嘗試禁用它,看看會發生什么。

Socket.BeginConnectSocket.BeginAccept絕對有用。 我相信他們在實現中使用ConnectExAcceptEx調用。 這些調用將初始連接協商和數據傳輸包裝到一個用戶/內核轉換中。 由於初始發送/接收緩沖區已准備就緒,因此內核可以將其發送到遠程主機或用戶空間。

它們還有一個准備好的偵聽器/連接器隊列,這可能通過避免用戶空間接受/接收連接並將其切斷(以及所有用戶/內核切換)所涉及的延遲來提供一些提升。

要將BeginConnect與緩沖區一起使用,似乎必須在連接之前將初始數據寫入套接字。

我想要消除的一件事是它不像垃圾收集器運行那么簡單。 如果所有消息都在堆上,則每秒生成10000個對象。

每100秒閱讀一次垃圾收集

唯一的解決方案是將郵件保留在堆中。

我有7年或8年前的相同問題和100毫秒到1秒暫停,問題是垃圾收集..從4 gig使用大約400兆但但有很多對象。

我最終用C ++存儲消息,但您可以使用ASP.NET緩存(以前使用COM並將它們移出堆中)

我沒有答案,但為了獲得更多信息,我建議將您的代碼與計時器一起使用,並記錄可疑操作所需的平均和最大時間,例如添加到隊列或打開套接字。

至少通過這種方式,您將了解要查看的內容以及從哪里開始。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM