Was ist NodeJS und wie funktioniert es?

Keylearnings:

  • Was ist NodeJS?
  • Was ist die V8 Engine?
  • Was sind Events und Event-Emitter?
  • Wie funktioniert die Event-Loop?
  • Wofür ist die Libuv Library zuständig?

Kürzlich war ich ein Eis essen!

Nicht alleine! Sondern mit einem Freund und seiner Tochter Lisa.

Lisa ist fünf Jahre alt. Mein Freund ist 31 und heißt Markus aber das tut nichts zur Sache.

Normalerweise mag Lisa am liebsten das gute alte Himbeereis. Genau so eines, welches auch Markus in seiner Kindheit immer für 40 Pfennig bekommen hat.

Nur dieses eine mal nicht. Jetzt nahm sie ein chemisch hellbläulich gefärbtes sogenanntes Schlumpf-Eis.

Sie wollte was neues! Sie suchte den Kick!

Dafür hatte ich Verständnis, denn so ging es mir kürzlich auch. Ich wollte neues ausprobieren. Ich suchte den Thrill!

Und so kam ich auf NodeJS, über welches ich in diesem Artikel mit dir sprechen möchte.

Was ist NodeJS?

Kennst du JavaScript?

Genau! Das ist die Sprache, die in deinem Browser läuft und dafür sorgt, dass eine Webseite dynamische Inhalte haben kann.

Und jetzt können wir damit sogar Server-Applikationen schreiben. Danke NodeJS!

Cool? Absolut! Denn das bedeutet, dass wir Frontend und Backend in der gleichen Programmiersprache entwickeln können.

Sehen wir uns an wie das funktioniert.

Wie wird JavaScript Code in einem Browser ausgeführt?

JavaScript ist eine Interpreter-Sprache. Jeder Browser, der mit JavaScript umgehen kann, besitzt eine sogenannte JavaScript Engine, die den Code interpretiert und ausführt.

Im Fall des Chrome Browser heißt diese Engine V8. Die V8 Engine ist in C++ geschrieben, gut optimiert und leistungsfähig.

Sicher ahnst du es schon.

Die einfachste Möglichkeit JavaScript Code auf dem Server laufen zu lassen, ist es die V8 Engine auf dem Server bereitzustellen.

Und genau das wird auch gemacht!

Leider ist das jedoch nur die halbe Miete.

JavaScript, das im Browser ausgeführt wird, unterliegt aus Sicherheitsgründen gewissen Restriktionen. So bietet die V8 z.B. keine Möglichkeiten auf das Dateisystem zuzugreifen oder auf direktem Weg Funktionen des Betriebssystems zu verwenden.

Von der Notwendigkeit Datenbanken benutzen zu müssen ganz zu schweigen.

Um brauchbare Backend-Services zu entwickeln sind solche Optionen aber dringend notwendig.

Daher enthält das NodeJS Ecosystem weitere Kernelemente wie die NodeJS Core Library, welche ebenfalls in C++ entwickelt ist und Features bereitstellt, die über die Funktionalität der V8 hinausgehen. Wie beispielsweise Methoden, mit denen http Requests und Responses verarbeitet werden können.

Node JS Komponenten Libuv

Für die in NodeJS Core enthaltenen Funktionen werden Wrapper bereitgestellt, die es ermöglichen die C++ Implementierungen mittels JavaScript Code aufzurufen.

Eine andere zentrale Komponente ist die sogenannte Libuv, die ein guter Grund dafür ist, weshalb NodeJS ein Superstar geworden ist.

Die Libuv realisiert nämlich die Event-Loop, welche dafür sorgt, dass es sich bei NodeJS um eine „event getriebene asynchrone JavaScript Laufzeitumgebung “ handelt.

What??

Ja, so habe ich auch reagiert, als ich das zum ersten mal gelesen habe. Dröseln wir das ganze mal nacheinander auf.

Die Sache mit diesem JavaScript haben wir bereits geklärt.

Machen wir weiter mit dem Begriff Event.

Event und Event-Emitter

Was passiert in deiner Stadt wenn ein Haus brennt?

Hoffentlich rückt dann die Feuerwehr aus!

Das Ereignis „Haus brennt“ führt also zu einer Reaktion bei der Feuerwehr.

Und genau das ist ein Event. Ein Event ist nichts weiter als ein Ereignis, auf das jemand anderes reagiert.

Die Teilnehmer, welche auf ein Ereignis reagieren nennen wir subscriber. Ein subscriber des Ereignisses „Haus brennt“ ist also unter anderem die Feuerwehr.

Mit Hilfe eines sogenannten Event-Emitters werden die Subscriber über das Eintreten eines bestimmten Ereignis informiert. Bei der Feuerwehr übernimmt die Rolle des Event-Emitters  die Notrufzentrale.

Fein, fein! Knacken wir die nächste Nuss und wenden uns dem Begriff asynchron zu.

Parallelität und Callbacks

Ein JavaScript Programm ist Single-Threaded.

Oder einfacher gesagt: Ein JavaScript Programm kann sich zu einem Zeitpunkt nur um genau eine einzige Sache kümmern.

Grund hierfür ist die JavaScript Engine, beispielsweise die V8, welche nur in einem Thread läuft.

Für eine Server-Anwendung ist das ziemlich schlecht. Man denke hierbei beispielsweise an Netflix, deren Server tausende von Anfragen zum gleichen Zeitpunkt zu bewältigen haben.

Aber zum Glück besteht Node JS nicht nur aus der V8 Engine sondern stellt zusätzlich auch die Libuv Library zur Verfügung, die eine parallele Ausführung ermöglicht.

Das passiert auf zweierlei Arten. Zum einen erzeugt die Libuv standardmäßig einen Threadpool der Größe vier, auf den Aufgaben verteilt werden können.

Zum anderen kann die Libuv Tasks an das Betriebssytem weitergeben, welches sich dann um die Terminierung kümmert . Ein klassisches Beispiel, in dem das passiert, sind http Verbindungen. Dies ist insbesondere wichtig, wenn wir mit Hilfe des NodeJS Moduls Express einen Webserver aufsetzen wollen.

Schauen wir uns zunächst den Fall an, dass die Libuv auf den Threadpool zurückgreift und lernen hierbei gleichzeitig das Konzept von Callback Funktionen kennen.

Multithreading in NodeJS 

Zum Zweck der Demonstration verwenden wir das standardmäßig in NodeJS integrierte Kryptografie-Modul crypto, das einige rechenintensive kryptografische Algorithmen zur Verfügung stellt.

Dieses Modul ist auch deshalb für unser Experiment hervorragend geeignet, weil es hier die meisten Funktionen in zwei Ausführungen gibt. Nämlich in einer synchronen und einer asynchronen Variante wodurch wir eine gute Möglichkeit zum Vergleich haben.

Wir schauen uns im folgenden die Methode pbkdf2 an, mit der ein Klartext-Passwort in einen Hashwert umgewandelt werden kann.

Diese Funktion wird benötigt, weil wir Passwörter aus Sicherheitsgründen niemals im Klartext abspeichern dürfen.

Beginnen wir mit der synchron ablaufenden Variante pbkdf2Sync.

Welche Bildschirmausgabe liefert uns folgendes Beispielprogramm?

1: console.log('Erster Aufruf!');
2: crypto.pbkdf2Sync('password','salt',100000, 512, 'sha512');
3: console.log('Zweiter Auftruf!');
4: crypto.pbkdf2Sync('password','salt',100000, 512, 'sha512');

Stop! Zunächst ein Wort zu den Parametern der Methode pbkdf2Sync.

Als ersten Parameter haben wir das Klartext-Passwort, welches in einen Hash umgewandelt werden soll.

Der zweite Parameter entspricht dem sogenannten Salt, einer nur uns bekannten Zeichenkette, die wir benötigen, um zu überprüfen, ob ein im Klartext eingegebenes Passwort zum in der Datenbank gespeicherten Hashwert passt.

Mittels des dritten Parameters geben wir an, mit wie vielen Iterationen des pbkdf2 Algorithmus der Hashwert berechnet werden soll. Je höher die Anzahl der Iterationen desto schwieriger ist es zwar von dem Hashwert auf das Klartext-Passwort zu schließen, allerdings erhöht sich mit ansteigender Iterationszahl auch die Laufzeit.

Parameter vier legt die Länge des Hashwertes fest und Parameter fünf bestimmt den Hash-Algorithmus, mit dem die pbkdf2 Methode arbeiten soll.

Für uns am wichtigsten ist, dass dieser Vorgang recht rechenintensiv ist und Zeit in Anspruch nimmt.

Die Programmausgabe ist wenig überraschend.

Erster Aufruf!
Zweiter Auftruf!

Sicher ist dir die Pause zwischen der Ausgabe von Erster Aufruf und Zweiter Aufruf aufgefallen. Das liegt daran, dass die Zeilen 1-4 der Reihe nach abgearbeitet werden und insbesondere Zeile drei erst ausgeführt wird, nachdem pbkdf2Sync in Zeile zwei durchgelaufen ist.

NodeJS synchron

Asynchronität in Node JS

Okay, kommen wir nun zur asynchronen Variante und nutzen hierbei gleich die Möglichkeit das Konzept einer Callback Funktion kennenzulernen.

Die asynchrone Version unseres pbkdf2Sync Beispiels, welche die Methode pbkdf2 anstatt pbkdf2Sync verwendet, sieht wie folgt aus:

1: console.log('Erster Aufruf!');
2: crypto.pbkdf2('password','salt',100000, 512, 'sha512',(err, hash) => console.log('Verarbeite ersten ermittelten hash!'));
3: console.log('Zweiter Auftruf!');
4: crypto.pbkdf2('password','salt',100000, 512, 'sha512',(err, hash) => console.log('Verarbeite zweiten ermittelten hash!'));

Als erstes fällt der zusätzliche Parameter in der Parameterliste von pbkdf2 auf. Genau das ist die sogenannte Callback Funktion.

In JavaScript haben wir die Möglichkeit anonyme Funktionen als Parameter zu übergeben und in der aktuellen Version können wir das mithilfe von sogenannten Arrow-Functions bewerkstelligen.

Die Syntax von Arrow Functions ist wie folgt:

(Parameterliste)  => Auszuführende Anweisungen

Der Teil vor dem => entspricht einer üblichen Parameterliste, in der Variablen stehen, auf die wir im Anweisungsteil auf der rechten Seite des Pfeils => zugreifen können.

So weit so gut. Aber was hat es jetzt mit den Callback Funktionen auf sich. Wozu brauchen wir diese?

Eine Callback Funktion ist einfach eine Funktion, die wir beim Aufruf einer anderen Funktion als Parameter übergeben. Die von uns aufgerufene Funktion verwendet dann die übergebene Callback Funktion um beispielsweise berechnete Werte weiterzuverarbeiten.

Okay, werden wir konkret, reden tacheles und schauen uns an was das konkret in unserem pbkdf2 Beispiel bedeutet.

Im Idealfall bestimmt die pbkdf2 Methode den zu einem Klartext-Passwort gehörenden Hashwert, oder es tritt ein Fehler auf.

Und genau das entspricht den Parametern in der Parameterliste unserer Callback Funktion. Der erste Parameter err enthält im Fehlerfall einen Errorcode. Läuft alles glatt, so steht im Parameter hash der zum Klartext-Passwort gehörende Hashwert.

Beide Parameter können anschließend im Anweisungsteil der Callback Funktion behandelt werden. Wir haben es uns an dieser Stelle leicht gemacht und geben hier einfach den Text Verarbeite ersten ermittelten hash! bzw. Verarbeite zweiten ermittelten hash!aus.

Auf zur Stunde der Wahrheit. Schauen wir uns die Bildschirmausgabe unseres Programmes an.

1: Erster Aufruf!
2: Zweiter Auftruf!
3: Verarbeite ersten ermittelten hash!
4: Verarbeite zweiten ermittelten hash!

Sieht auf den ersten Blick nach einem Druckfehler aus. Ist aber vollkommen korrekt. Das Programm wird nämlich jetzt asynchron ausgeführt.

Da wir nun die asynchrone Variante der Methode pbkdf2 verwenden, muss mit der Ausführung der dritten Zeile nicht gewartet werden bis die Abarbeitung von pbkdf2 abgeschlossen ist.

Die beiden Aufrufe von pbkdf2 in den Zeilen zwei und vier werden auf zwei verschiedene Threads verteilt, und unter der Voraussetzung, dass dein Rechner mindestens zwei Prozessorkerne besitzt, parallel ausgeführt.

Das erklärt auch das Laufzeitverhalten.

Node JS asynchron

Die ersten beiden Zeilen erscheinen unmittelbar nachdem wir das Programm gestartet haben. Bis die dritte Ausgabe auftaucht dauert es zwar ein wenig, dafür erscheint diese quasi gleichzeitig mit Zeile vier.

Nicht immer ist das Verteilen auf Threads die beste Variante. Multithreading ist ein guter Ansatz, wenn es darum geht rechenintensive Tasks zu verteilen, aber beispielsweise ist bei einem Datei-Download nicht die Rechenleistung sondern die Netzwerkverbindung der Flaschenhals. Daher würde ein Thread bei einem Task dieser Art die meiste Zeit mit warten verbringen.

In solchen Fällen liefern C++ Methoden (asynchrone primitiv Typen), mit denen Aufgaben an das Betriebssystem delegiert werden, die Lösung.

Eine letzte Frage bleibt noch zu klären. Wie bekommt NodeJS mit, dass ein asynchron ablaufender Task abgeschlossen wurde. Hierfür verantwortlich ist die sogenannten NodeJS Event Loop.

Die Event Loop

Die NodeJS Event Loop ist die Brücke zwischen dem sequentiell ablaufenden Funktionsaufrufen und den durch die Node API (Node Core, Libuv etc.) initiierten asynchron abzuarbeitenden Tasks.

Sobald wir ein NodeJS Programm starten wird das Programm vollständig auf einen Callstack gelegt und hier von oben nach unten abgearbeitet.

Hierbei ist der Callstack ein als Stack organisierter Speicherbereich.

Jede ausgeführte Programmzeile wird vom Stack entfernt.

Code der parallel bzw. nebenläufig ausgeführt werden soll, wird zusammen mit einer Callback Funktion an die NodeJS API übergeben.

In dem Moment, in dem die Verarbeitung abgeschlossen ist, betritt ein weiterer Teilnehmer des NodeJS System die Bühne. Nämlich die Callback Queue, in der, nachdem die asynchron abzuarbeitenden Aufgaben erledigt wurden, die Callback Funktion zur Abarbeitung bereitgestellt wird.

Zwischen dem Callstack und der Callback Queue läuft die Event Loop in einer Dauerschleife und überprüft, ob der Callstack leer ist und in der Callback Queue eine Callback Funktion bereit steht.

Ist das der Fall wird die Callback Funktion auf den Callstack gelegt und durch die V8 Engine ausgeführt.

Node JS Event Loop

Fazit: In diesem Artikel haben wir besprochen um was es sich bei NodeJS handelt. Wir haben festgestellt, dass ein JavaScript Programm in einem einzigen Thread abläuft, was insbesondere für serverseitige Anwendungen ungeeignet ist. NodeJS löst dieses Problem mit Hilfe von C++ Bibliotheken wie die Libuv.

Des Weiteren haben wir uns angesehen was es mit der NodeJS Event Loop auf sich hat. Zusätzlich ermöglicht uns NodeJS Backend und Frontend in der gleichen Programmiersprache zu entwickeln.

Ziel dieses Artikel war es dir einen Überblick darüber zu verschaffen wie NodeJS funktioniert.

Hat dir der Artikel gefallen? Dann folge uns doch am besten gleich auf Facebook oder auf Instagram!

Ich freue mich auf deine Fragen im Kommentarbereich!

Hallo ich bin Kim und ich möchte ein großer Programmierer werden. Machst du mit?

Kommentare (0)

Hinterlasse ein Kommentar