1. Introduction à Netty
Netty est un framework NIO asynchrone hautes performances basé sur JAVA NIO Implémentation de l'API fournie. Il prend en charge TCP, UDP et le transfert de fichiers. En tant que framework NIO asynchrone, toutes les opérations IO de Netty sont asynchrones et non bloquantes. Grâce au mécanisme Future-Listener, les utilisateurs peuvent facilement obtenir les opérations IO de manière active ou via le mécanisme de notification. . En tant que framework NIO le plus populaire actuellement, Netty a été largement utilisé dans le domaine de l'Internet, de l'informatique distribuée Big Data, de l'industrie du jeu, de l'industrie des communications, etc. Certains composants open source bien connus de l'industrie sont également construits sur la base du framework NIO de Netty.
2. Modèle de thread Netty
En termes de JAVA NIO, Selector fournit la base du mode Reactor Netty combine les modes Selector et Reactor pour concevoir un thread efficace. modèle. Jetons d'abord un coup d'œil au modèle Reactor :
2.1 Modèle Reactor
Wikipédia explique le modèle Reactor de cette façon : « Le modèle de conception du réacteur est un modèle de gestion d'événements pour gérer les demandes de service délivrées simultanément par une ou plusieurs entrées. Le gestionnaire de service démultiplexe ensuite les demandes entrantes et les distribue de manière synchrone aux gestionnaires de demandes associés. Tout d'abord, le mode Reactor est piloté par les événements. Il existe une ou plusieurs sources d'entrée simultanées, un gestionnaire de serveur et plusieurs gestionnaires de demandes. Ce gestionnaire de services multiplexera de manière synchrone les demandes d'entrée et les distribuera aux gestionnaires de demandes correspondants. Cela peut être montré dans la figure ci-dessous :
La structure est quelque peu similaire aux modèles producteur et consommateur, c'est-à-dire qu'un ou plusieurs producteurs mettent des événements dans une file d'attente, et a Ou plusieurs consommateurs interrogent activement les événements de cette file d'attente pour les traiter ; alors que le mode Reactor ne dispose pas de file d'attente pour la mise en mémoire tampon. Chaque fois qu'un événement est entré dans le gestionnaire de services, le gestionnaire de services le distribuera activement à différents événements en fonction de différents types d'événements. . Le gestionnaire de requêtes correspondant est utilisé pour le gérer.
2.2 Implémentation du modèle Reator
Concernant Java NIO construisant le modèle Reator, Doug Lea a donné une bonne explication dans "Scalable IO in Java". Voici une interception du PPT sur l'implémentation. du modèle Reator. Description
1. Le premier modèle d'implémentation est le suivant :
Il s'agit du modèle monothread Reactor le plus simple, car le Reactor Le mode utilise des E/S asynchrones non bloquantes, toutes les opérations d'E/S ne seront pas bloquées. En théorie, un thread peut gérer toutes les opérations d'E/S indépendamment. À l’heure actuelle, le thread Reactor est un généraliste, responsable du démultiplexage des sockets, de l’acceptation de nouvelles connexions et de la distribution des requêtes à la chaîne de traitement.
Pour certains scénarios d'applications de petite capacité, le modèle monothread peut être utilisé. Cependant, il ne convient pas aux applications à forte charge et à grande concurrence. Les principales raisons sont les suivantes :
(1) Lorsqu'un thread NIO traite des centaines ou des milliers de liens en même temps, les performances ne peuvent pas être améliorées. être pris en charge, même si le CPU du thread NIO Même si la charge atteint 100%, le message ne peut pas être entièrement traité
(2) Lorsque le thread NIO est surchargé, la vitesse de traitement ralentira, ce qui ralentira Cela entraîne l'expiration d'un grand nombre de connexions client. Après l'expiration du délai, le message sera souvent retransmis, ce qui réduit encore la charge du thread NIO.
(3) Faible fiabilité. Une boucle infinie inattendue d'un thread entraînera l'indisponibilité de l'ensemble du système de communication.
Afin de résoudre ces problèmes, le modèle multithread Reactor a émergé.
2. Modèle multi-threading Reactor :
Par rapport au modèle précédent, ce modèle utilise le multi-threading (pool de threads) dans la partie chaîne de traitement. .
Dans la plupart des scénarios, ce modèle peut répondre aux exigences de performances. Cependant, dans certains scénarios d'application spéciaux, par exemple, le serveur effectuera une authentification de sécurité sur le message d'établissement de liaison du client. Dans de tels scénarios, un seul thread Accepteur peut souffrir de performances insuffisantes. Afin de résoudre ces problèmes, le troisième modèle de thread Reactor a été produit.
Recommandations associées : "Tutoriel de développement Java"
3. Modèle maître-esclave de Reactor
Par rapport au deuxième modèle, ce modèle divise le Reactor en deux parties. Le mainReactor est responsable de la surveillance du socket serveur et de l'acceptation des nouvelles connexions et attribue le socket établi au subReactor. Le sous-réacteur est responsable du démultiplexage des sockets connectés, de la lecture et de l'écriture des données réseau et de leur transfert vers le pool de threads de travail pour les fonctions de traitement métier. Habituellement, le nombre de sous-réacteurs peut être égal au nombre de processeurs.
Modèle Netty 2.3
Après avoir parlé des trois modèles de Reactor en 2.2, lequel est Netty ? En fait, le modèle de thread de Netty est une variante du modèle Reactor, qui est la troisième forme de variante qui supprime le pool de threads. C'est également le mode par défaut de Netty NIO. Les participants du mode Reactor dans Netty incluent principalement les composants suivants :
(1) Selector
(2) EventLoopGroup/EventLoop
(3) ChannelPipeline
Selector est le multiplexeur SelectableChannel fourni dans NIO, qui joue le rôle de démultiplexeur. Je n'entrerai pas dans les détails ici. Les deux autres fonctions et leurs rôles dans le mode Reactor de Netty sont présentés ci-dessous.
3. EventLoopGroup/EventLoop
Lorsque le système est en cours d'exécution, des changements fréquents de contexte de thread entraîneront des pertes de performances supplémentaires. Lorsque plusieurs threads exécutent un processus métier simultanément, les développeurs métier doivent également être vigilants à tout moment sur la sécurité des threads. Quelles données peuvent être modifiées simultanément et comment les protéger ? Cela réduit non seulement l’efficacité du développement, mais entraîne également des pertes de performances supplémentaires.
Afin de résoudre les problèmes ci-dessus, Netty adopte le concept de conception de sérialisation De la lecture des messages, de l'encodage et de l'exécution ultérieure du gestionnaire, le thread IO EventLoop est toujours responsable, ce qui signifie que l'ensemble du processus ne sera pas responsable. procédez lorsque le contexte du thread est changé, les données ne risquent pas d'être modifiées simultanément. Cela explique également pourquoi le modèle de thread Netty supprime le pool de threads dans le modèle maître-esclave de Reactor.
EventLoopGroup est une abstraction d'un groupe de EventLoops. EventLoopGroup fournit l'interface suivante, qui peut être utilisée pour obtenir l'une des EventLoops dans un groupe de EventLoops selon certaines règles pour traiter les tâches. à propos d'EventLoopGroup, c'est qu'il se trouve dans Netty et sur le serveur Netty. En programmation, nous avons besoin de deux EventLoopGroups, BossEventLoopGroup et WorkerEventLoopGroup, pour fonctionner. Habituellement, un port de service, c'est-à-dire un ServerSocketChannel, correspond à un sélecteur et à un thread EventLoop, ce qui signifie que le paramètre numéro de thread de BossEventLoopGroup est 1. BossEventLoop est responsable de la réception de la connexion du client et de la transmission de SocketChannel à WorkerEventLoopGroup pour le traitement des E/S.
L'implémentation d'EventLoop fait office de Dispatcher dans le modèle Reactor.
4. ChannelPipeline
ChannelPipeline joue en fait le rôle de processeur de requêtes en mode Reactor.
L'implémentation par défaut de ChannelPipeline est DefaultChannelPipeline. DefaultChannelPipeline lui-même maintient une queue et une tête ChannelHandler invisibles pour l'utilisateur, qui sont situées respectivement en tête et en queue de la file d'attente de la liste chaînée. La queue est dans la partie supérieure et la tête est dans la direction la plus proche de la couche réseau. Il existe deux interfaces importantes pour ChannelHandler dans Netty, ChannelInBoundHandler et ChannelOutBoundHandler. Les flux entrants peuvent être compris comme le flux de données réseau de l'extérieur vers l'intérieur du système, et les flux sortants peuvent être compris comme le flux de données réseau de l'intérieur du système vers l'extérieur du système. Le ChannelHandler implémenté par l'utilisateur peut implémenter une ou plusieurs interfaces selon les besoins et les placer dans la file d'attente de la liste chaînée dans le Pipeline. Le ChannelPipeline trouvera le gestionnaire correspondant à traiter en fonction des différents types d'événements IO. La file d'attente de liste chaînée est en mode chaîne de responsabilité. Une variante, descendante ou ascendante, tous les gestionnaires qui satisfont à la corrélation d'événement traiteront l'événement.
ChannelInBoundHandler traite les messages envoyés du client au serveur. Il est généralement utilisé pour effectuer des demi-paquets/paquets collants, le décodage, la lecture de données, le traitement métier, etc. ; client. Pour le traitement, il est généralement utilisé pour encoder et envoyer des messages au client.
La figure suivante est une illustration du processus d'exécution de ChannelPipeline :
Pour plus d'informations sur Pipeline, veuillez vous référer à : Un bref exposé sur le modèle de pipeline (Pipeline)
5. Buffer
Le Buffer étendu fourni par Netty présente de nombreux avantages par rapport à NIO En tant qu'élément d'accès aux données très important, jetons un coup d'œil à. Netty. Quelles sont les caractéristiques de Buffer ?
1. Pointeurs de lecture et d'écriture ByteBuf
Dans ByteBuffer, les pointeurs de lecture et d'écriture sont respectivement position, tandis que dans ByteBuf, les pointeurs de lecture et d'écriture sont respectivement readerIndex etwriterIndex. Un pointeur réalise les fonctions de deux pointeurs, en enregistrant les variables. Cependant, lors du changement d'état de lecture et d'écriture de ByteBuffer, la méthode flip doit être appelée avant d'écrire la prochaine fois, le contenu du Buffer doit être lu puis appelé la méthode clear. . Appelez flip avant chaque lecture et clear avant d'écrire. Cela entraîne sans aucun doute des étapes fastidieuses de développement, et le contenu ne peut pas être écrit tant que le contenu n'est pas lu, ce qui est très rigide. En revanche, regardons ByteBuf. Lors de la lecture, il s'appuie uniquement sur le pointeur readerIndex. Lors de l'écriture, il s'appuie uniquement sur le pointeurwriterIndex. Il n'est pas nécessaire d'appeler la méthode correspondante avant chaque lecture et écriture, et il n'y a pas de limite. à tout lire d'un coup.
2. Zéro copie
(1) Netty utilise DIRECT BUFFERS pour recevoir et envoyer ByteBuffer, en utilisant la mémoire directe hors tas pour la lecture et l'écriture de Socket, sans avoir besoin d'une copie secondaire du tampon d'octets. Si vous utilisez la mémoire tas traditionnelle (HEAP BUFFERS) pour la lecture et l'écriture du Socket, la JVM copiera le tampon de mémoire tas dans la mémoire directe, puis l'écrira sur le Socket. Par rapport à la mémoire directe en dehors du tas, le message dispose d'une copie mémoire supplémentaire du tampon pendant le processus d'envoi.
(2) Netty fournit un objet Buffer combiné, qui peut regrouper plusieurs objets ByteBuffer. Les utilisateurs peuvent utiliser le Buffer combiné aussi facilement qu'un seul Buffer, évitant ainsi la méthode traditionnelle de copie de mémoire. Les tampons sont fusionnés en un seul grand Buffer. .
(3) Le transfert de fichiers de Netty utilise la méthode transferTo, qui peut envoyer directement les données du tampon de fichier au canal cible, évitant ainsi le problème de copie de mémoire causé par la méthode d'écriture cyclique traditionnelle.
3. Technologie de comptage et de pooling de références
Dans Netty, chaque Buffer appliqué peut être une ressource très précieuse pour Netty, donc afin d'obtenir une application de mémoire et de récupérer plus de contrôle, Netty implémente la mémoire gestion basée sur le comptage de références. L'utilisation de Buffer par Netty est basée sur la mémoire directe (DirectBuffer), ce qui améliore considérablement l'efficacité des opérations d'E/S. Cependant, en plus de la haute efficacité des opérations d'E/S, DirectBuffer et HeapBuffer ont également un inconvénient naturel, c'est-à-dire. L'application pour DirectBuffer est moins efficace que HeapBuffer, donc Netty implémente PolledBuffer en combinaison avec le comptage de références, c'est-à-dire l'utilisation en pool. Lorsque le nombre de références est égal à 0, Netty recyclera le tampon dans le pool et personne ne le fera. s'applique pour le tampon la prochaine fois. Les moments seront réutilisés.
Résumé
Netty est essentiellement l'implémentation du modèle Reactor, avec Selector comme multiplexeur, EventLoop comme redirecteur et Pipeline comme processeur d'événements. Mais contrairement aux réacteurs ordinaires, Netty utilise la sérialisation et utilise le modèle de chaîne de responsabilité dans Pipeline.
Le tampon de Netty a été optimisé par rapport au tampon de NIO, ce qui améliore considérablement les performances.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!