web-dev-qa-db-fra.com

Java thread par modèle de connexion vs NIO

Le NIO non bloquant Java NIO est-il toujours plus lent que votre thread standard par socket asynchrone de connexion?

De plus, si vous deviez utiliser des threads par connexion, créeriez-vous simplement de nouveaux threads ou utiliseriez-vous un très grand pool de threads?

J'écris un serveur MMORPG dans Java qui devrait être capable de faire évoluer facilement 10000 clients avec un matériel suffisamment puissant, bien que le nombre maximum de clients soit 24000 ( que je crois impossible à atteindre pour le modèle de thread par connexion en raison d'une limite de 15 000 threads en Java.) Dans un article de trois ans, j'ai entendu dire que le blocage IO avec un thread par le modèle de connexion était encore 25% plus rapide que NIO (à savoir, ce document http://www.mailinator.com/tymaPaulMultithreaded.pdf ), mais peut-on encore en arriver ce jour? Java a beaucoup changé depuis lors, et j'ai entendu dire que les résultats étaient douteux lors de la comparaison de scénarios réels car le VM utilisé n'était pas Sun Java. De plus, parce que il s'agit d'un serveur MMORPG avec de nombreux utilisateurs simultanés interagissant les uns avec les autres, l'utilisation de la synchronisation et des pratiques de sécurité des threads réduira-t-elle les performances au point où un sélecteur NIO à thread unique desservant 10000 clients w mal être plus rapide? (tout le travail ne doit pas nécessairement être traité sur le fil avec le sélecteur, il peut être traité sur les fils de travail comme le fonctionnement de MINA/Netty).

Merci!

39
Kevin Jin

Les avantages NIO doivent être pris avec un grain de sel.

Dans un serveur HTTP, la plupart des connexions sont des connexions permanentes, elles sont inactives la plupart du temps. Ce serait un gaspillage de ressources de pré-allouer un thread pour chacun.

Pour MMORPG les choses sont très différentes. Je suppose que les connexions sont constamment occupées à recevoir des instructions des utilisateurs et à envoyer le dernier état du système aux utilisateurs. Un thread est nécessaire la plupart du temps pour une connexion.

Si vous utilisez NIO, vous devrez constamment réallouer un thread pour une connexion. Il peut s'agir d'une solution inférieure à la solution simple à fil fixe par connexion.

La taille de la pile de threads par défaut est assez grande (1/4 Mo?), C'est la principale raison pour laquelle il ne peut y avoir qu'un nombre limité de threads. Essayez de le réduire et voyez si votre système peut en prendre davantage en charge.

Cependant si votre jeu est en effet très "occupé", c'est votre CPU qui vous inquiète le plus. NIO ou non, il est vraiment difficile de gérer des milliers de joueurs hyper actifs sur une machine.

23
irreputable

Il existe en fait 3 solutions:

  1. Plusieurs threads
  2. Un fil et NIO
  3. Les deux solutions 1 et 2 en même temps

La meilleure chose à faire pour les performances est d'avoir un petit nombre limité de threads et d'événements réseau multiplex sur ces threads avec NIO lorsque de nouveaux messages arrivent sur le réseau.


L'utilisation de NIO avec un thread est une mauvaise idée pour plusieurs raisons:

  • Si vous avez plusieurs processeurs ou cœurs, vous utiliserez les ressources au ralenti car vous ne pouvez utiliser qu'un seul cœur à la fois si vous n'avez qu'un seul thread.
  • Si vous devez bloquer pour une raison quelconque (peut-être pour faire un accès au disque), votre processeur est inactif lorsque vous pourriez gérer une autre connexion pendant que vous attendez le disque.

Un thread par connexion est une mauvaise idée car il ne s'adapte pas. Disons avoir:

  • 10 000 connexions
  • 2 processeurs avec 2 cœurs chacun
  • seuls 100 threads seront bloqués à un moment donné

Ensuite, vous pouvez comprendre que vous n'avez besoin que de 104 threads. Plus et vous gaspillez des ressources en gérant des threads supplémentaires dont vous n'avez pas besoin. Il y a beaucoup de comptabilité sous le capot pour gérer 10 000 threads. Cela vous ralentira.


C'est pourquoi vous combinez les deux solutions. Assurez-vous également que votre VM utilise les appels système les plus rapides. Chaque système d'exploitation a ses propres appels système uniques pour les E/S réseau hautes performances. Assurez-vous que votre VM est en utilisant le dernier et le plus grand. Je crois que c'est epoll () sous Linux.

De plus, si vous deviez utiliser des threads par connexion, créeriez-vous simplement de nouveaux threads ou utiliseriez-vous un très grand pool de threads?

Cela dépend du temps que vous souhaitez consacrer à l'optimisation. La solution la plus rapide consiste à créer des ressources telles que des threads et des chaînes en cas de besoin. Ensuite, laissez la collecte des ordures les réclamer lorsque vous avez terminé avec eux. Vous pouvez obtenir une amélioration des performances en disposant d'un pool de ressources. Au lieu de créer un nouvel objet, vous en demandez un au pool et le renvoyez au pool lorsque vous avez terminé. Cela ajoute la complexité du contrôle d'accès simultané. Cela peut être encore optimisé avec des algorithmes de concurrence avancés comme algorithmes non bloquants . Les nouvelles versions de l'API Java Java en ont quelques-unes pour vous. Vous pouvez passer le reste de votre vie à faire ces optimisations sur un seul programme. La meilleure solution pour votre application spécifique est probablement une question qui mérite son propre poste.

12
Jay

Si vous êtes prêt à dépenser n'importe quelle somme d'argent sur un matériel suffisamment puissant, pourquoi vous limiter à un seul serveur. Google n'utilise pas un serveur, ils n'utilisent même pas un centre de données de serveurs.

Une idée fausse commune est que NIO autorise le non-blocage IO c'est pourquoi son seul modèle mérite une analyse comparative. Si vous comparez le blocage de NIO, vous pouvez l'obtenir 30% plus rapidement que les anciennes E/S, c'est-à-dire si vous utilisez le même modèle de filetage et comparer uniquement les modèles IO.

Pour un jeu sophistiqué, vous êtes beaucoup plus susceptible de manquer de CPU avant de toucher les connexions 10K. Encore une fois, il est plus simple d'avoir une solution qui évolue horizontalement. Ensuite, vous n'avez pas à vous soucier du nombre de connexions que vous pouvez obtenir.

Combien d'utilisateurs peuvent raisonnablement interagir? 24? dans ce cas, 1000 groupes indépendants interagissent. Vous n'aurez pas autant de cœurs sur un seul serveur.

Combien d'argent par utilisateur comptez-vous dépenser sur le (s) serveur (s)? Vous pouvez acheter un serveur 12 cœurs avec 64 Go de mémoire pour moins de 5 000 £. Si vous placez 2500 utilisateurs sur ce serveur, vous avez dépensé 2 £ par utilisateur.

EDIT: J'ai une référence http://vanillajava.blogspot.com/2010/07/Java-nio-is-faster-than-Java-io-for.html qui est la mienne. ;) J'ai fait réviser cela par quelqu'un qui est un GURU de Java Networking et il était largement d'accord avec ce qu'il avait trouvé.

9
Peter Lawrey

Si vos connexions sont occupées, ce qui signifie qu’elles vous envoient constamment des données et que vous les renvoyez, vous pouvez utiliser non-Blocking IO en conjonction avec Akka.

Akka est une boîte à outils open source et un runtime simplifiant la construction d'applications simultanées et distribuées sur la JVM. Akka prend en charge plusieurs modèles de programmation pour la concurrence, mais il met l'accent sur la concurrence basée sur les acteurs, en s'inspirant d'Erlang. Des liaisons de langage existent pour les deux Java et Scala.

La logique d'Akka est non bloquante, elle est donc parfaite pour la programmation asynchrone. En utilisant Akka Actors vous pouvez supprimer Thread overhead.

Mais si vos flux de socket se bloquent plus souvent, je suggère d'utiliser Blocking IO en conjonction avec Quasar

Quasar est une bibliothèque open source pour une concurrence JVM simple et légère, qui implémente de véritables threads légers (fibres AKA) sur la JVM. Les fibres Quasar se comportent comme des threads simples Java, sauf qu'ils n'ont pratiquement pas de mémoire et de surcharge de commutation de tâches, de sorte que vous pouvez facilement générer des centaines de milliers de fibres - voire des millions - dans une seule machine virtuelle Java Quasar fournit également des canaux de communications inter-fibres sur le modèle de ceux proposés par le langage Go, avec des sélecteurs de canaux. Il contient également une implémentation complète du modèle d'acteur, étroitement modelé sur Erlang.

La logique de Quasar est bloquante, vous pouvez donc engendrer, disons 24000 fibres en attente sur différentes connexions. L'un des points positifs de Quasar est que les fibres peuvent interagir très facilement avec les fils simples. Quasar a également des intégrations avec des bibliothèques populaires, telles que Apache HTTP client ou JDBC ou Jersey et ainsi de suite, afin que vous puissiez utiliser les avantages de l'utilisation de fibres dans de nombreux aspects de votre projet.
Vous pouvez voir une bonne comparaison entre ces deux cadres ici .

3
Alireza Mohamadi

Comme la plupart d'entre vous disent que le serveur doit être verrouillé dans l'utilisation du processeur avant que 10 000 utilisateurs simultanés ne soient atteints, je suppose qu'il est préférable pour moi d'utiliser une approche IO de blocage (N) filetée compte tenu du fait que pour ce cas particulier MMORPG, obtenir plusieurs paquets par seconde pour chaque joueur n'est pas rare et pourrait enliser un sélecteur si un devait être utilisé.

Peter a soulevé un point intéressant: le blocage de NIO est plus rapide que les anciennes bibliothèques, tandis qu'irréfutable a mentionné que pour un serveur occupé MMORPG, il serait préférable d'utiliser des threads en raison du nombre d'instructions reçues par joueur. Je ne compterais pas sur trop de joueurs inactifs sur ce jeu, donc cela ne devrait pas être un problème pour moi d'avoir un tas de threads non actifs. Je me suis rendu compte que la synchronisation est toujours requise même lorsque vous utilisez un framework basé sur NIO car ils utilisent plusieurs threads de travail s'exécutant en même temps pour traiter les paquets reçus des clients. Le changement de contexte peut s'avérer coûteux, mais je vais essayer cette solution. Il est relativement facile de refactoriser mon code pour que je pourrait utiliser un cadre NIO si je trouve qu'il y a un goulot d'étranglement.

Je crois que ma question a été répondue. J'attendrai encore un peu afin de recevoir encore plus d'informations de plus de gens. Merci pour toutes vos réponses!

EDIT: J'ai finalement choisi mon plan d'action. En fait, j'étais indécis et j'ai décidé d'utiliser JBoss Netty et de permettre à l'utilisateur de basculer entre oio ou nio en utilisant les classes

org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory;

Assez bien que Netty supporte les deux!

2
Kevin Jin

Vous pourriez vous inspirer de l'ancien projet parrainé par Sun, maintenant appelé Red Dwarf. L'ancien site Web à http://www.reddwarfserver.org/ est en panne.
Github à la rescousse: https://github.com/reddwarf-nextgen/reddwarf

1
Jochen Bedersdorfer

Si vous effectuez des appels réseau côté client, vous n'avez probablement besoin que d'un socket ordinaire io.

Si vous créez des technologies côté serveur, NIO vous aidera à séparer la partie io du réseau du travail de traitement/exécution. IO threads configurés comme 1 ou 2 pour le réseau IO. Les threads de travail sont pour la partie de traitement réelle (qui varie de 1 à N, en fonction des capacités de la machine).

0
Venkateswara Rao