Node.js Server mit mehreren gleichzeitigen Anfragen, wie funktioniert es?

8

Ich weiß, dass node.js ein single-threaded, asynchroner, nicht blockierender E / A ist. Ich habe viel darüber gelesen. Zum Beispiel verwendet PHP einen Thread pro Anfrage, aber Knoten verwendet nur einen Thread für alle, so.

Angenommen, es gibt drei Anfragen a, b, c, die gleichzeitig auf dem Server node.js ankommen. Drei dieser Anforderungen erfordern eine große Blockierungsoperation, z. B. möchten alle die gleiche große Datei lesen.

Wie werden dann die Anfragen in die Warteschlange gestellt, in welcher Reihenfolge wird die Blockierung durchgeführt und in welchen Sequenzen werden die Antworten versendet? Natürlich mit wievielen Threads?

Bitte sagen Sie mir die Sequenzen von Anfrage zu Antwort für drei Anfragen.

    
Siddharth 11.04.2016, 07:29
quelle

1 Antwort

29

Im Folgenden finden Sie eine Beschreibung einer Abfolge von Ereignissen für Ihre drei Anforderungen:

  1. Drei Anfragen werden an den node.js-Webserver gesendet.
  2. Welche Anfrage auch immer vor den beiden anderen ankommt, löst den Web-Server-Request-Handler aus und startet die Ausführung.
  3. Die anderen beiden Anfragen gehen in die Ereigniswarteschlange node.js und warten, bis sie an der Reihe sind. Es hängt technisch von den Interna der Implementierung von node.js ab, ob eine wartende Anfrage auf der TCP-Eingangsebene in der Warteschlange steht oder ob sie innerhalb von node.js eingereiht ist (ich weiß es eigentlich nicht), aber für die Zwecke dieser Diskussion alle Entscheidend ist, dass das eingehende Ereignis in die Warteschlange gestellt wird und erst ausgelöst wird, wenn die erste Anforderung nicht mehr ausgeführt wird.
  4. Dieser erste Anforderungshandler wird ausgeführt, bis er eine asynchrone Operation (z. B. das Lesen einer Datei) ausführt und dann nichts anderes mehr zu tun hat, bis die asynchrone Operation abgeschlossen ist.
  5. An diesem Punkt wird die asynchrone Datei-E / A-Operation initiiert und der ursprüngliche Anforderungshandler kehrt zurück (es wird mit dem gemacht, was er in diesem Moment tun kann).
  6. Da die erste Anfrage (die auf Datei-E / A wartet) für jetzt zurückgegeben wurde, kann die node.js-Engine jetzt das nächste Ereignis aus der Ereigniswarteschlange ziehen und starten. Dies ist die zweite Anfrage, die auf dem Server eintrifft. Es wird den gleichen Prozess bei der ersten Anfrage durchlaufen und wird laufen, bis es nichts anderes zu tun hat (und auch auf Datei-I / O wartet).
  7. Wenn die zweiten Anforderungen an das System zurückgegeben werden (weil es auf Datei-E / A wartet), kann die dritte Anforderung gestartet werden. Es wird den gleichen Weg wie die vorherigen beiden folgen.
  8. Wenn die dritte Anforderung nun ebenfalls auf E / A wartet und zum System zurückkehrt, kann node.js das nächste Ereignis aus der Ereigniswarteschlange ziehen.
  9. Zu diesem Zeitpunkt sind alle drei Anforderungshandler gleichzeitig "im Flug". Nur einer läuft tatsächlich gleichzeitig, aber alle sind gleichzeitig im Prozess.
  10. Dieses nächste Ereignis in der Ereigniswarteschlange könnte ein anderes Ereignis oder eine andere Anforderung sein, oder es könnte der Abschluss einer der drei vorherigen Datei-E / A-Operationen sein. Welches Ereignis als nächstes in der Warteschlange auftritt, beginnt mit der Ausführung. Angenommen, es handelt sich um die Datei-E / A-Operation der ersten Anfrage. An diesem Punkt ruft es den Beendigungsrückruf auf, der mit der Datei-E / A-Operation der ersten Anforderung verknüpft ist, und diese erste Anforderung beginnt mit der Verarbeitung der Datei-E / A-Ergebnisse. Dieser Code wird dann weiter ausgeführt, bis er entweder die gesamte Anforderung beendet und zurückgibt oder bis er eine andere asynchrone Operation (wie z. B. mehr Datei-E / A) startet und zurückgibt.
  11. Eventuell ist der Datei-I / O der zweiten Anfrage bereit und dieses Ereignis wird aus der Ereigniswarteschlange gezogen.
  12. Dann das gleiche für die dritte Anfrage und schließlich werden alle drei enden.

Obwohl also nur eine Anfrage gleichzeitig ausgeführt wird, können mehrere Anfragen gleichzeitig "in Bearbeitung" oder "im Flug" sein. Dies wird manchmal als kooperatives Multitasking bezeichnet, anstelle von "präemptivem" Multitasking mit mehreren nativen Threads, bei denen das System jederzeit zwischen Threads wechseln kann. Ein bestimmter Javascript-Thread wird ausgeführt, bis er zum System und dann zurückkehrt und nur dann kann ein anderes Stück Javascript gestartet werden. Da ein Teil von Javascript nicht blockierende asynchrone Operationen auslösen kann, kann der JavaScript-Thread zum System zurückkehren (wodurch andere Teile von Javascript aktiviert werden), während asynchrone Operationen noch ausstehen. Wenn diese Vorgänge abgeschlossen sind, werden sie ein Ereignis in die Ereigniswarteschlange stellen und wenn anderes Javascript ausgeführt wird und dieses Ereignis an den Anfang der Warteschlange gelangt, wird es ausgeführt.

Single-Threaded

Der entscheidende Punkt hier ist, dass ein bestimmter Javascript-Thread ausgeführt wird, bis er zum System zurückkehrt. Wenn während der Ausführung einige asynchrone Vorgänge (z. B. Datei-I / O oder Netzwerk) gestartet werden, wird nach Abschluss dieser Ereignisse ein Ereignis in die Ereigniswarteschlange eingefügt, und wenn die JS-Engine fertig ist, werden zuvor alle Ereignisse ausgeführt Dieses Ereignis wird bearbeitet und ein Callback wird aufgerufen und der Callback wird ausgeführt.

Diese single-threaded-Eigenschaft vereinfacht die Behandlung von Parallelität im Vergleich zu einem Multi-Threaded-Modell erheblich. In einer Multithread-Umgebung, in der jede einzelne Anfrage ihren eigenen Thread startet, sind JEDE Daten, die geteilt werden sollen, selbst eine einfache Variable, einer Race Condition ausgesetzt und müssen mit einem Mutex geschützt werden, bevor jemand sie lesen kann.

In Javascript, da keine gleichzeitige Ausführung mehrerer Anfragen erfolgt, wird kein Mutex für den einfachen Zugriff auf gemeinsam genutzte Variablen benötigt. An dem Punkt, an dem Javascript eine Variable liest, wird per Definition kein anderes Javascript in diesem Moment ausgeführt (single threaded).

Knoten.js verwendet Threads

Eine technische Besonderheit ist, dass nur die Ausführung Ihres Javascript single threaded ist. Die Interna von node.js verwenden für einige Dinge selbst Threads. Zum Beispiel verwendet asynchrone Datei-E / A tatsächlich native Threads. Netzwerk-E / A verwendet keine Threads (es verwendet native ereignisgesteuerte Netzwerke).

Aber diese Verwendung von Threads in den Interna von node.js hat keinen direkten Einfluss auf die Ausführung von Javascript. Es gibt immer nur einen einzigen Thread von Javascript, der gleichzeitig ausgeführt wird.

Rassenbedingungen

Es kann immer noch Race-Bedingungen für den Zustand geben, der gerade geändert wird, wenn eine asynchrone Operation gestartet wird. Dies ist jedoch viel seltener als in einer Umgebung mit mehreren Threads und viel einfacher zu identifizieren und zu schützen diese Fälle. Als ein Beispiel für eine Race Condition, die existieren kann, habe ich einen einfachen Server, der Messwerte von mehreren Temperatursonden alle 10 Sekunden mit einem Intervall-Timer misst. Er sammelt die Daten von all diesen Temperaturmessungen und schreibt diese Daten jede Stunde auf die Festplatte. Es verwendet asynchrone E / A, um die Daten auf die Festplatte zu schreiben. Da jedoch verschiedene asynchrone Datei-E / A-Vorgänge zum Schreiben der Daten auf die Festplatte verwendet werden, ist es möglich, dass der Intervallzeitgeber zwischen einigen dieser asynchronen Datei-E / A-Vorgänge ausgelöst wird, die die Daten verursachen, auf denen sich der Server befindet die Mitte des Schreibens auf die Festplatte geändert werden. Dies ist schlecht und kann dazu führen, dass inkonsistente Daten geschrieben werden. In einer einfachen Welt könnte dies vermieden werden, indem eine Kopie aller Daten erstellt wird, bevor sie mit dem Schreiben auf die Festplatte beginnt. Wenn also eine neue Temperaturmessung erfolgt, während die Daten auf die Festplatte geschrieben werden, ist die Kopie nicht betroffen wird immer noch einen konsistenten Datensatz auf die Festplatte schreiben. Aber im Fall dieses Servers können die Daten groß sein und der Speicher auf dem Server ist klein (es ist ein Raspberry Pi Server), so dass es nicht praktisch ist, eine Kopie aller Daten im Speicher zu erstellen.

Das Problem wird also gelöst, indem ein Flag gesetzt wird, wenn die Daten gerade auf den Datenträger geschrieben werden, und dann das Flag gelöscht wird, wenn die Daten auf den Datenträger geschrieben wurden. Wenn ein Intervallzeitgeber ausgelöst wird, während dieses Flag gesetzt ist, werden die neuen Daten in eine separate Warteschlange gestellt, und die Kerndaten, die gerade auf die Festplatte geschrieben werden, werden NICHT geändert. Wenn die Daten fertig sind, werden sie auf die Platte geschrieben, und sie prüft die Warteschlange und alle Temperaturdaten, die dort gefunden werden, werden dann zu den Temperaturdaten im Speicher hinzugefügt. Die Integrität dessen, was gerade auf die Festplatte geschrieben wird, bleibt erhalten. Mein Server protokolliert jedes Mal ein Ereignis, wenn diese "Wettlaufbedingung" getroffen wird und Daten deswegen in die Warteschlange gestellt werden. Und, siehe da, es passiert hin und wieder und der Code, um die Integrität der Daten zu erhalten, funktioniert.

    
jfriend00 11.04.2016, 07:48
quelle