webentwicklung-frage-antwort-db.com.de

Tipps/Techniken für leistungsstarke C # -Server-Sockets

Ich besitze einen .NET 2.0-Server, auf dem offenbar Skalierungsprobleme auftreten, wahrscheinlich aufgrund eines schlechten Entwurfs des Socket-Handhabungscodes, und ich suche nach Anleitungen, wie ich ihn möglicherweise neu entwerfen kann, um die Leistung zu verbessern.

Verwendungsszenario: 50 - 150 Clients, hohe Rate (bis zu 100s/Sekunde) von kleinen Nachrichten (jeweils 10s Bytes) zu/von jedem Client. Client-Verbindungen sind langlebig - in der Regel Stunden. (Der Server ist Teil eines Handelssystems. Die Client-Nachrichten werden zu Gruppen zusammengefasst, um sie über eine geringere Anzahl von "ausgehenden" Socket-Verbindungen an eine Vermittlungsstelle zu senden, und Bestätigungsnachrichten werden an die Clients zurückgesendet, während jede Gruppe von der Vermittlungsstelle verarbeitet wird .) Betriebssystem ist Windows Server 2003, Hardware ist 2 x 4-Core X5355.

Aktuelles Client-Socket-Design: Eine TcpListener erzeugt einen Thread, um jedes Client-Socket zu lesen, wenn Clients eine Verbindung herstellen. Die Threads blockieren Socket.Receive, analysieren eingehende Nachrichten und fügen sie in eine Reihe von Warteschlangen ein, damit sie von der Core Server-Logik verarbeitet werden können. Bestätigungsnachrichten werden mithilfe von asynchronen Socket.BeginSend-Aufrufen von den Threads, die mit der Vermittlungsstelle kommunizieren, über die Client-Sockets zurückgesendet.

Beobachtete Probleme: Mit zunehmender Anzahl der Clients (jetzt 60-70) stellen wir zeitweise Verzögerungen von bis zu 100 Millisekunden fest, während Daten an die Clients gesendet und von diesen empfangen werden. (Wir protokollieren Zeitstempel für jede Bestätigungsnachricht, und wir können gelegentlich lange Lücken in der Zeitstempelsequenz für Bündel von Bestätigungen derselben Gruppe sehen, die normalerweise insgesamt in wenigen ms ausgehen.)

Die CPU-Auslastung des Gesamtsystems ist gering (<10%), es gibt genügend freien RAM, und die Kernlogik und die ausgehende Seite (Exchange-Seite) funktionieren einwandfrei. Daher scheint das Problem auf den Client-Socket-Code beschränkt zu sein . Zwischen dem Server und den Clients besteht eine ausreichende Netzwerkbandbreite (Gigabit-LAN), und wir haben Probleme mit dem Netzwerk oder der Hardware-Schicht ausgeschlossen.

Anregungen oder Hinweise auf nützliche Ressourcen wären sehr dankbar. Wenn jemand Diagnose- oder Debugging-Tipps hat, um herauszufinden, was genau schief läuft, sind diese auch großartig.

Hinweis: Ich habe den MSDN Magazine-Artikel Winsock: Mit Hochleistungs-Sockets in .NET näher am Draht und ich habe einen Blick auf die Kodart-Komponente "XF.Server" geworfen - sie sieht bestenfalls skizzenhaft aus .

32
McKenzieG1

Vieles hat damit zu tun, dass auf Ihrem System viele Threads ausgeführt werden und der Kernel jedem von ihnen eine Zeitscheibe gibt. Das Design ist einfach, aber nicht gut skalierbar.

Sie sollten wahrscheinlich Socket.BeginReceive verwenden, das in den .net-Threadpools ausgeführt wird (Sie können die Anzahl der von ihm verwendeten Threads in gewisser Weise angeben) und dann über den asynchronen Rückruf (der in einem beliebigen der ausgeführt werden kann) auf eine Warteschlange pushen .NET-Threads). Dies sollte Ihnen eine viel höhere Leistung bringen.

18
grepsedawk

Die Socket-E/A-Leistung wurde in der .NET 3.5-Umgebung verbessert. Sie können ReceiveAsync/SendAsync anstelle von BeginReceive/BeginSend verwenden, um die Leistung zu verbessern. Chech this out:

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

22
hakkyu

Ein Thread pro Client scheint massiv überfordert zu sein, insbesondere angesichts der insgesamt geringen CPU-Auslastung. Normalerweise möchten Sie, dass ein kleiner Pool von Threads alle Clients bedient und BeginReceive verwendet, um auf die asynchrone Arbeit zu warten. Senden Sie dann die Verarbeitung einfach an einen der Worker (möglicherweise einfach, indem Sie die Arbeit zu einer synchronisierten Warteschlange hinzufügen, auf die alle Worker warten) ).

8
Marc Gravell

Ich bin kein C # -Typ, aber für Hochleistungs-Socket-Server ist die skalierbarste Lösung die Verwendung von I/O Completion Ports mit einer Anzahl aktiver Threads, die für die CPU (s) geeignet sind, die der Prozess ausführt aktiviert, anstatt das Ein-Thread-pro-Verbindung-Modell zu verwenden.

In Ihrem Fall möchten Sie mit einem 8-Core-Rechner 16 Threads mit 8 Threads gleichzeitig ausführen. (Die anderen 8 werden grundsätzlich in Reserve gehalten.)

6
John Dibling

Wie andere vorgeschlagen haben, besteht die beste Möglichkeit, dies zu implementieren, darin, den clientseitigen Code vollständig asynchron zu machen. Verwenden Sie BeginAccept () auf dem TcpServer (), damit Sie keinen Thread manuell erzeugen müssen. Verwenden Sie dann BeginRead ()/BeginWrite () für den zugrunde liegenden Netzwerkstream, den Sie vom akzeptierten TcpClient erhalten.

Es gibt jedoch eine Sache, die ich hier nicht verstehe. Sie sagten, dass dies langlebige Verbindungen und eine große Anzahl von Kunden sind. Angenommen, das System hat den eingeschwungenen Zustand erreicht, in dem Ihre maximalen Clients (z. B. 70) verbunden sind. Sie haben 70 Threads, die auf die Client-Pakete warten. Dann sollte das System noch reagieren. Es sei denn, Ihre Anwendung weist Speicher-/Handle-Lecks auf und Ihnen gehen die Ressourcen aus, sodass auf Ihrem Server ein Paging ausgeführt wird. Ich würde einen Timer um den Aufruf von Accept () setzen, in dem Sie einen Client-Thread starten und sehen, wie viel Zeit das dauert. Außerdem würde ich Taskmanager und PerfMon starten und "Non Paged Pool", "Virtual Memory" und "Handle Count" für die App überwachen und feststellen, ob sich die App in einer Ressourcenkrise befindet.

Async ist zwar der richtige Weg, aber ich bin nicht überzeugt, ob es das zugrunde liegende Problem wirklich lösen wird. Ich würde die App überwachen, wie ich es vorgeschlagen habe, und sicherstellen, dass es keine Probleme mit Speicherverlusten und Handles gibt. In dieser Hinsicht war "BigBlackMan" oben richtig - Sie benötigen mehr Instrumente, um fortzufahren. Ich weiß nicht, warum er abgelehnt wurde.

4
feroze

Die Socket.BeginConnect und Socket.BeginAccept sind definitiv nützlich. Ich glaube, sie verwenden die Aufrufe ConnectEx und AcceptEx in ihrer Implementierung. Diese Aufrufe vereinen die anfängliche Verbindungsverhandlung und Datenübertragung in einem Benutzer/Kernel-Übergang. Da der anfängliche Sende-/Empfangspuffer bereits bereit ist, kann der Kernel ihn einfach abschicken - entweder an den Remote-Host oder an den Userspace.

Sie haben auch eine Warteschlange von Listenern/Konnektoren parat, was wahrscheinlich ein wenig Auftrieb gibt, indem die Wartezeit vermieden wird, die mit dem Akzeptieren/Empfangen einer Verbindung durch den Userspace und deren Weitergabe (und dem gesamten Benutzer-/Kernel-Switching) verbunden ist.

Um BeginConnect mit einem Puffer zu verwenden, müssen Sie anscheinend die Anfangsdaten in den Socket schreiben, bevor Sie eine Verbindung herstellen.

3
Luke Quinane

Zufällige intermittierende Verzögerungen von ~ 250 ms sind möglicherweise auf den von TCP verwendeten Nagle-Algorithmus zurückzuführen. Deaktivieren Sie das und sehen Sie, was passiert.

3
Addys

Eine Sache, die ich beseitigen möchte, ist, dass es nicht so einfach ist, wie der Garbage Collector. Wenn sich alle Ihre Nachrichten auf dem Heap befinden, generieren Sie 10000 Objekte pro Sekunde.

Lesen Sie alle 100 Sekunden Garbage Collection

Die einzige Lösung besteht darin, Ihre Nachrichten vom Haufen zu halten.

1
Tom Thorne

Ich hatte das gleiche Problem vor 7 oder 8 Jahren und Pausen von 100 ms bis 1 Sek., Das Problem war Garbage Collection. Hatte ungefähr 400 Meg im Einsatz von 4 Gig, ABER es gab viele Objekte.

Am Ende habe ich Nachrichten in C++ gespeichert, aber Sie konnten den ASP.NET-Cache verwenden (der früher COM verwendet hat und sie aus dem Heap verschoben hat).

0
user1496062