web-dev-qa-db-fra.com

Conseils/techniques pour les sockets de serveur C # hautes performances

J'ai un serveur .NET 2.0 qui semble rencontrer des problèmes de dimensionnement, probablement à cause d'une mauvaise conception du code de traitement des sockets, et je cherche des conseils sur la manière dont je pourrais le redéfinir pour améliorer les performances.

Scénario d'utilisation: 50 - 150 clients, débit élevé (jusqu'à 100 s/seconde) de petits messages (10 octets chacun) à destination/en provenance de chaque client. Les connexions client durent longtemps - généralement plusieurs heures. (Le serveur fait partie d’un système de trading. Les messages client sont regroupés pour être envoyés à un échange sur un plus petit nombre de connexions de socket 'sortantes', et des accusés de réception sont renvoyés aux clients lorsque chaque groupe est traité par l’échange. .) OS est Windows Server 2003, le matériel est 2 x 4-core X5355.

Conception actuelle du socket client: A TcpListener génère un thread qui lit chaque socket client lors de la connexion des clients. Les threads se bloquent sur Socket.Receive en analysant les messages entrants et en les insérant dans un ensemble de files d'attente en vue de leur traitement par la logique du serveur principal. Les messages d'accusé de réception sont renvoyés sur les sockets client à l'aide d'appels asynchrones Socket.BeginSend des threads qui parlent au côté de l'échange.

Problèmes observés: À mesure que le nombre de clients a augmenté (actuellement entre 60 et 70), nous avons commencé à observer des retards intermittents pouvant aller jusqu'à 100 millisecondes lors de l'envoi et de la réception de données vers/depuis les clients. (Nous enregistrons des horodatages pour chaque message d'accusé de réception et nous pouvons constater de longs écarts occasionnels dans la séquence d'horodatage pour des groupes d'acquises du même groupe qui sortent normalement en quelques ms au total.)

L'utilisation globale du processeur par le système est faible (<10%), la quantité de mémoire RAM disponible est importante, et la logique principale et le côté sortant (côté échange) fonctionnent correctement. Le problème semble donc isolé du code de socket côté client. . Il existe une large bande passante réseau entre le serveur et les clients (réseau local gigabit) et nous avons éliminé les problèmes de réseau ou de couche matérielle.

Toute suggestion ou indication de ressources utiles serait grandement appréciée. Si quelqu'un a des conseils de diagnostic ou de débogage pour savoir exactement ce qui ne va pas, ils seraient également utiles.

Remarque: j'ai l'article MSDN Magazine/ Winsock: familiarisez-vous avec les connexions haute performance dans .NET , et j'ai jeté un coup d'œil sur le composant Kodart "XF.Server". .

32
McKenzieG1

Cela tient en grande partie au fait que de nombreux threads s'exécutent sur votre système et que le noyau leur attribue une tranche de temps. Le design est simple, mais ne s'adapte pas bien.

Vous devriez probablement utiliser Socket.BeginReceive qui sera exécuté sur les pools de threads .net (vous pouvez spécifier le nombre de threads utilisés) Threads .NET). Cela devrait vous donner des performances beaucoup plus élevées.

18
grepsedawk

Les performances d'E/S de socket se sont améliorées dans l'environnement .NET 3.5. Vous pouvez utiliser ReceiveAsync/SendAsync au lieu de BeginReceive/BeginSend pour de meilleures performances. Chech cela dehors:

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

22
hakkyu

Un thread par client semble excessivement lourd, en particulier compte tenu de la faible utilisation globale du processeur ici. Normalement, vous voudriez un petit groupe de threads desservant tous les clients, en utilisant BeginReceive pour attendre que le travail soit asynchrone - puis envoyez simplement le traitement à l'un des travailleurs (peut-être simplement en ajoutant le travail à une file d'attente synchronisée sur laquelle tous les travailleurs attendent ).

8
Marc Gravell

Je ne suis certes pas un type C #, mais pour les serveurs de socket hautes performances, la solution la plus évolutive consiste à utiliser Ports de complétion E/S avec un nombre de threads actifs correspondant aux processeurs exécutés sur, plutôt que d'utiliser le modèle à un thread par connexion.

Dans votre cas, avec une machine à 8 cœurs, vous voudriez que 16 threads au total et 8 exécutés simultanément. (Les 8 autres sont essentiellement détenus en réserve.)

6
John Dibling

Comme d'autres l'ont suggéré, le meilleur moyen de le mettre en œuvre serait de rendre le code client confronté à un code asynchrone. Utilisez BeginAccept () sur le serveur TcpServer () pour ne pas avoir à générer manuellement un thread. Ensuite, utilisez BeginRead ()/BeginWrite () sur le flux de réseau sous-jacent que vous obtenez à partir de TcpClient accepté.

Cependant, il y a une chose que je ne comprends pas ici. Vous avez dit qu'il s'agissait de relations de longue durée et d'un grand nombre de clients. En supposant que le système ait atteint un état stable, votre maximum de clients (70 par exemple) est connecté. Vous avez 70 threads à l'écoute pour les paquets du client. Ensuite, le système devrait toujours être réactif. Sauf si votre application a des fuites de mémoire/gestion et que vous manquez de ressources, votre serveur effectue une pagination. Je voudrais mettre une minuterie autour de l'appel à Accept () où vous démarrez un thread client et voyez combien de temps cela prend. En outre, je voudrais démarrer taskmanager et PerfMon, et surveiller les "pools non paginés", "mémoire virtuelle", "nombre de poignées" pour l'application et voir si l'application est dans un état critique de ressources.

Bien qu'il soit vrai qu'utiliser Async soit la bonne solution, je ne suis pas convaincu si cela résoudra réellement le problème sous-jacent. Je voudrais surveiller l'application comme je l'ai suggéré et m'assurer qu'il n'y a pas de problèmes intrinsèques de fuites de mémoire et de poignées. À cet égard, "BigBlackMan" ci-dessus était exact - vous avez besoin de plus d'instruments pour continuer. Je ne sais pas pourquoi il a été voté.

4
feroze

Les Socket.BeginConnect et Socket.BeginAccept sont vraiment utiles. Je crois qu'ils utilisent les appels ConnectEx et AcceptEx / dans leur implémentation. Ces appels encapsulent la négociation de connexion initiale et le transfert de données dans une transition utilisateur/noyau. Puisque le tampon d’envoi/réception initial est déjà prêt, le noyau peut simplement l’envoyer, soit à l’hôte distant, soit à l’espace utilisateur.

Ils ont également une file d’écouteurs/connecteurs prête à utiliser, ce qui donne probablement un coup de pouce en évitant la latence impliquée dans l’acceptation/la réception par l’utilisateur de la connexion et par sa transmission (ainsi que par tous les changements d’utilisateur/noyau).

Pour utiliser BeginConnect avec un tampon, il semble que vous deviez écrire les données initiales sur le socket avant de vous connecter.

3
Luke Quinane

Des retards aléatoires intermittents ~ 250 ms peuvent être dus à l'algorithme de Nagle utilisé par TCP. Essayez de désactiver cela et voyez ce qui se passe.

3
Addys

Une chose que je voudrais éliminer, c'est que ce n'est pas quelque chose d'aussi simple que le garbage collector en cours d'exécution. Si tous vos messages sont sur le tas, vous générez 10000 objets par seconde.

Prenez une lecture de Ramasse-miettes toutes les 100 secondes

La seule solution est de garder vos messages du tas.

1
Tom Thorne

J'avais le même problème il y a 7 ou 8 ans et des pauses de 100 ms à 1 seconde. Le problème était le ramassage des ordures. Si environ 400 Mo étaient utilisés à partir de 4 Go MAIS il y avait beaucoup d'objets.

J'ai fini par stocker des messages en C++, mais vous pouvez utiliser le cache ASP.NET (qui utilisait COM et les a sortis du tas)

0
user1496062