1. Einführung in Netty
Netty ist ein leistungsstarkes, asynchrones ereignisgesteuertes NIO-Framework, das auf basiert JAVA NIO bereitgestellte API-Implementierung. Es bietet Unterstützung für TCP, UDP und Dateiübertragung. Alle E/A-Vorgänge von Netty sind asynchron und nicht blockierend. Über den Future-Listener-Mechanismus können Benutzer E/A-Vorgänge problemlos aktiv oder über den Benachrichtigungsmechanismus abrufen . Netty ist derzeit das beliebteste NIO-Framework und wird häufig in den Bereichen Internet, verteiltes Big-Data-Computing, Spieleindustrie, Kommunikationsindustrie usw. verwendet. Einige bekannte Open-Source-Komponenten in der Branche basieren auch auf dem NIO-Framework von Netty.
2. Netty-Thread-Modell
In Bezug auf JAVA NIO bietet Selector die Grundlage für den Reactor-Modus und kombiniert die Modi Selector und Reactor, um einen effizienten Thread zu entwerfen Modell. Werfen wir zunächst einen Blick auf das Reactor-Muster:
2.1 Reactor-Muster
Wikipedia erklärt das Reactor-Modell folgendermaßen: „Das Reactor-Designmuster ist ein Ereignisbehandlungsmuster für die gleichzeitige Bearbeitung von Serviceanfragen, die von gesendet werden.“ Der Service-Handler demultiplext dann die eingehenden Anfragen und sendet sie synchron an zugehörige Anfrage-Handler. Erstens ist der Reaktormodus ereignisgesteuert. Es gibt eine oder mehrere gleichzeitige Eingabequellen, einen Server-Handler und mehrere Anforderungshandler. Dieser Service-Handler multiplext die Eingabeanforderungen und verteilt sie an die entsprechenden Anforderungshandler. Es kann in der folgenden Abbildung dargestellt werden:
Die Struktur ähnelt in gewisser Weise den Produzenten- und Konsumentenmodellen, das heißt, ein oder mehrere Produzenten stellen Ereignisse in eine Warteschlange und a Oder mehrere Verbraucher rufen aktiv Ereignisse aus dieser Warteschlange zur Verarbeitung ab, während der Reaktormodus keine Warteschlange zum Puffern hat. Immer wenn ein Ereignis in den Service-Handler eingegeben wird, verteilt der Service-Handler es aktiv an verschiedene Ereignisse entsprechend unterschiedlicher Ereignistypen Zur Bearbeitung wird der entsprechende Request-Handler verwendet.
2.2 Implementierung des Reator-Musters
In Bezug auf die Konstruktion des Reator-Musters durch Java NIO gab Doug Lea in „Scalable IO in Java“ eine gute Erklärung des PPT zur Implementierung des Reator-Musters. Beschreibung
1. Das erste Implementierungsmodell ist wie folgt:
Dies ist das einfachste Reactor-Single-Thread-Modell, da der Reactor Der Modus verwendet asynchrone, nicht blockierende E/A, alle E/A-Vorgänge werden nicht blockiert. Theoretisch kann ein Thread alle E/A-Vorgänge unabhängig verarbeiten. Zu diesem Zeitpunkt ist der Reactor-Thread ein Generalist, der für das Demultiplexen von Sockets, das Akzeptieren neuer Verbindungen und das Verteilen von Anforderungen an die Verarbeitungskette verantwortlich ist.
Für einige Anwendungsszenarien mit geringer Kapazität kann das Single-Threaded-Modell verwendet werden. Es ist jedoch nicht für Anwendungen mit hoher Last und großer Parallelität geeignet. Die Hauptgründe sind folgende:
(1) Wenn ein NIO-Thread Hunderte oder Tausende von Links gleichzeitig verarbeitet, kann die Leistung nicht beeinträchtigt werden unterstützt werden, auch wenn die CPU des NIO-Threads selbst wenn die Auslastung 100 % erreicht, die Nachricht nicht vollständig verarbeitet werden kann
(2) Wenn der NIO-Thread überlastet ist, verlangsamt sich die Verarbeitungsgeschwindigkeit Bei einer großen Anzahl von Clientverbindungen kommt es zu einer Zeitüberschreitung, wodurch die Nachricht häufig erneut übertragen wird, was die Belastung des NIO-Threads noch weiter erhöht.
(3) Eine unerwartete Endlosschleife eines Threads führt dazu, dass das gesamte Kommunikationssystem nicht verfügbar ist.
Um diese Probleme zu lösen, wurde das Reactor-Multithreading-Modell entwickelt.
2. Reactor-Multithreading-Modell:
Im Vergleich zum Vorgängermodell verwendet dieses Modell Multithreading (Thread-Pool) im Verarbeitungskettenteil .
In den meisten Szenarien kann dieses Modell die Leistungsanforderungen erfüllen. In einigen speziellen Anwendungsszenarien führt der Server jedoch beispielsweise eine Sicherheitsauthentifizierung für die Handshake-Nachricht des Clients durch. In solchen Szenarien kann die Leistung eines einzelnen Akzeptor-Threads unzureichend sein. Um diese Probleme zu lösen, wurde das dritte Reactor-Thread-Modell erstellt.
Verwandte Empfehlungen: „Java-Entwicklungs-Tutorial“
3. Reactor-Master-Slave-Modell
Im Vergleich zum zweiten Modell unterteilt dieses Modell den Reactor in zwei Teile. Der MainReactor ist für die Überwachung des Server-Sockets und die Annahme neuer Verbindungen verantwortlich und weist den eingerichteten Socket dem SubReactor zu. Der SubReactor ist dafür verantwortlich, verbundene Sockets zu demultiplexen, Netzwerkdaten zu lesen und zu schreiben und sie für Geschäftsverarbeitungsfunktionen an den Worker-Thread-Pool zu übergeben. Normalerweise kann die Anzahl der Unterreaktoren der Anzahl der CPUs entsprechen.
2.3 Netty-Modell
Nachdem wir über die drei Modelle von Reactor in 2.2 gesprochen haben, welches ist Netty? Tatsächlich ist das Thread-Modell von Netty eine Variante des Reactor-Modells, der dritten Variante, die den Thread-Pool entfernt. Dies ist auch der Standardmodus von Netty NIO. Zu den Teilnehmern des Reactor-Modus in Netty gehören hauptsächlich die folgenden Komponenten:
(1) Selector
(2) EventLoopGroup/EventLoop
(3) ChannelPipeline
Selector ist der in NIO bereitgestellte SelectableChannel-Multiplexer, der die Rolle des Demultiplexers spielt. Die anderen beiden Funktionen und ihre Rollen im Netty-Reaktormodus werden im Folgenden vorgestellt.
3. EventLoopGroup/EventLoop
Wenn das System läuft, führt ein häufiger Thread-Kontextwechsel zu zusätzlichen Leistungsverlusten. Wenn mehrere Threads gleichzeitig einen Geschäftsprozess ausführen, müssen Geschäftsentwickler jederzeit auf die Thread-Sicherheit achten. Welche Daten können gleichzeitig geändert werden und wie können sie geschützt werden? Dies verringert nicht nur die Entwicklungseffizienz, sondern verursacht auch zusätzliche Leistungseinbußen.
Um die oben genannten Probleme zu lösen, übernimmt Netty das Serialisierungsdesignkonzept. Für das Lesen von Nachrichten, die Codierung und die anschließende Ausführung des Handlers ist immer der E/A-Thread EventLoop verantwortlich, was bedeutet, dass der gesamte Prozess nicht ausgeführt wird Wenn der Thread-Kontext geändert wird, besteht nicht das Risiko, dass die Daten gleichzeitig geändert werden. Dies erklärt auch, warum das Netty-Thread-Modell den Thread-Pool im Reactor-Master-Slave-Modell entfernt.
EventLoopGroup ist eine Abstraktion einer Gruppe von EventLoops. EventLoopGroup bietet die nächste Schnittstelle, die verwendet werden kann, um einen der EventLoops in einer Gruppe von EventLoops gemäß bestimmten Regeln zur Verarbeitung von Aufgaben abzurufen Über EventLoopGroup ist, dass es sich in Netty und auf dem Netty-Server befindet. Beim Programmieren benötigen wir zwei EventLoopGroups, BossEventLoopGroup und WorkerEventLoopGroup, um zu funktionieren. Normalerweise entspricht ein Service-Port, also ein ServerSocketChannel, einem Selector und einem EventLoop-Thread, was bedeutet, dass der Thread-Nummernparameter von BossEventLoopGroup 1 ist. BossEventLoop ist dafür verantwortlich, die Verbindung des Clients zu empfangen und SocketChannel zur E/A-Verarbeitung an WorkerEventLoopGroup zu übergeben.
Die Implementierung von EventLoop fungiert als Dispatcher im Reactor-Muster.
4. ChannelPipeline
ChannelPipeline spielt tatsächlich die Rolle des Anforderungsprozessors im Reaktormodus.
Die Standardimplementierung von ChannelPipeline ist DefaultChannelPipeline selbst und verwaltet einen für den Benutzer unsichtbaren Tail- und Head-ChannelHandler, die sich am Kopf bzw. am Ende der verknüpften Listenwarteschlange befinden. Der Schwanz befindet sich im oberen Teil und der Kopf befindet sich näher an der Netzwerkschicht. Es gibt zwei wichtige Schnittstellen für ChannelHandler in Netty: ChannelInBoundHandler und ChannelOutBoundHandler. Eingehend kann als der Fluss von Netzwerkdaten von außerhalb des Systems nach innen verstanden werden, und ausgehend kann als Fluss von Netzwerkdaten von der Innenseite des Systems nach außerhalb des Systems verstanden werden. Der vom Benutzer implementierte ChannelHandler kann nach Bedarf eine oder mehrere der Schnittstellen implementieren und in die verknüpfte Listenwarteschlange in der Pipeline stellen. Die ChannelPipeline findet den entsprechenden Handler, der entsprechend den verschiedenen E/A-Ereignistypen verarbeitet werden soll Die verknüpfte Listenwarteschlange befindet sich im Chain-of-Responsibility-Modus. Eine Variante, von oben nach unten oder von unten nach oben, alle Handler, die die Ereigniskorrelation erfüllen, verarbeiten das Ereignis.
ChannelInBoundHandler verarbeitet vom Client an den Server gesendete Nachrichten. Er wird im Allgemeinen zum Durchführen von Halbpaket-/Sticky-Paketen, zum Dekodieren, Lesen von Daten, zur Geschäftsverarbeitung usw. verwendet. ChannelOutBoundHandler verarbeitet vom Server an den Server gesendete Nachrichten Client-Verarbeitung, die im Allgemeinen zum Verschlüsseln und Senden von Nachrichten an den Client verwendet wird.
Die folgende Abbildung zeigt den Ausführungsprozess von ChannelPipeline:
Weitere Informationen zu Pipeline finden Sie unter: Ein kurzer Vortrag zum Pipeline-Modell (Pipeline)
5. Puffer
Der von Netty bereitgestellte erweiterte Puffer ist ein sehr wichtiger Datenzugriff Netty. Was sind die Eigenschaften von Buffer?
1. ByteBuf-Lese- und Schreibzeiger
In ByteBuffer sind die Lese- und Schreibzeiger Position, während in ByteBuf die Lese- und Schreibzeiger jeweils nur der ReaderIndex und der WriterIndex sind Ein Zeiger realisiert die Funktionen von zwei Zeigern und speichert Variablen. Beim Umschalten des Lese- und Schreibstatus von ByteBuffer muss jedoch vor dem nächsten Schreiben der Inhalt des Puffers gelesen und dann aufgerufen werden . Rufen Sie Flip vor jedem Lesen auf und klären Sie es vor dem Schreiben. Dies bringt zweifellos langwierige Entwicklungsschritte mit sich, und der Inhalt kann erst geschrieben werden, wenn er gelesen wurde, was sehr unflexibel ist. Schauen wir uns dagegen ByteBuf an. Beim Lesen ist es nur auf den Zeiger des Schreibers angewiesen. Es ist nicht erforderlich, die entsprechende Methode vor jedem Lese- und Schreibvorgang aufzurufen, und es gibt keine Begrenzung alles auf einmal zu lesen.
2. Keine Kopie
(1) Netty verwendet DIRECT BUFFERS zum Empfangen und Senden von ByteBuffer und verwendet Off-Heap-Direktspeicher zum Lesen und Schreiben von Sockets, ohne dass eine sekundäre Kopie des Bytepuffers erforderlich ist. Wenn herkömmlicher Heap-Speicher (HEAP BUFFERS) zum Lesen und Schreiben von Sockets verwendet wird, kopiert die JVM den Heap-Speicherpuffer in den Direktspeicher und schreibt ihn dann in den Socket. Im Vergleich zum direkten Speicher außerhalb des Heaps verfügt die Nachricht während des Sendevorgangs über eine zusätzliche Speicherkopie des Puffers.
(2) Netty bietet ein kombiniertes Pufferobjekt, das mehrere ByteBuffer-Objekte zusammenfassen kann. Benutzer können den kombinierten Puffer so bequem bedienen wie einen Puffer, wodurch die herkömmliche Methode des Speicherkopierens vermieden wird Puffer.
(3) Nettys Dateiübertragung verwendet die transferTo-Methode, mit der die Daten im Dateipuffer direkt an den Zielkanal gesendet werden können, wodurch das durch die herkömmliche zyklische Schreibmethode verursachte Speicherkopierproblem vermieden wird.
3. Referenzzähl- und Pooling-Technologie
In Netty kann jeder angewendete Puffer eine sehr wertvolle Ressource für Netty sein. Um also Speicheranwendung zu erhalten und mehr Kontrolle zurückzugewinnen, implementiert Netty Speicher Management basierend auf Referenzzählung. Nettys Verwendung von Buffer basiert auf Direktspeicher (DirectBuffer), was die Effizienz von E/A-Vorgängen erheblich verbessert. Zusätzlich zur hohen Effizienz von E/A-Vorgängen weisen DirectBuffer und HeapBuffer jedoch auch einen natürlichen Nachteil auf. Die Anwendung von DirectBuffer ist weniger effizient als HeapBuffer, daher implementiert Netty PolledBuffer in Kombination mit der Referenzzählung, d Puffer wird beim nächsten Mal wiederverwendet.
Zusammenfassung
Netty ist im Wesentlichen die Implementierung des Reactor-Musters, mit Selector als Multiplexer, EventLoop als Weiterleitung und Pipeline als Ereignisprozessor. Aber im Gegensatz zu gewöhnlichen Reaktoren verwendet Netty Serialisierung und das Chain-of-Responsibility-Modell in Pipeline.
Der Puffer in Netty wurde im Vergleich zum Puffer in NIO optimiert, was die Leistung erheblich verbessert.
Das obige ist der detaillierte Inhalt vonWas ist das Netty-Prinzip in Java?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!