Node.js - Professionell hochperformante Software entwickeln

Robert Prediger, Ralph Winzinger

Node.js

Professionell hochperformante Software entwickeln

2015

368 Seiten

Format: PDF, ePUB, Online Lesen

E-Book: €  34,99

E-Book kaufen

E-Book kaufen

ISBN: 9783446437586

 

1 Hello, Node.js

Was ist Node.js eigentlich und wie arbeitet man nun damit? Das ist die zentrale Frage, die im ersten Teil des Buchs beantwortet werden soll. Hier geht es nicht darum, welche Bibliotheken bei der Erstellung großer Softwareprojekte besonders hilfreich sind oder wie man dafür sorgt, dass eine Node.js-Anwendung stabil läuft, auch wenn sie eine Unmenge von Anfragen abarbeiten muss. Nein, dieser Teil des Buchs liegt zeitlich vor den ersten Zeilen Anwendungscode, die hoffentlich bald entstehen.

Am Anfang dürfen natürlich ein paar einführende Worte zu Node.js nicht fehlen. Woher kommt dieses Node eigentlich und was ist daran besonders? Die technischen Tiefen werden erst im letzten Teil des Buchs erreicht, aber eine Idee von der Funktionsweise sollte man trotzdem von Anfang an haben.

Und neben der Idee bedarf es natürlich auch noch des notwendigen Handwerkszeugs. Node.js braucht eine Laufzeitumgebung ? für welche Systeme existiert die und wo kann man sie bekommen? Mit welchen Tools kann Quellcode editiert werden? Wo findet man Bibliotheken und wie kann man diese benutzen? Wenn auch diese Fragen alle geklärt sind, hat man keine Ausreden mehr, sich die Finger am Code schmutzig zu machen. Und auch wenn man kein Enterprise-System realisieren möchte ? Spaß kann man mit Node.js allemal haben. Und das ist schon mal ein ganz wichtiger Punkt!

1.1 Einführung in Node.js

Node.js hat ein unglaubliches Wachstum hingelegt. Von der ersten Ankündigung durch Ryan Dahl 20091 bis heute (Ende 2014) sind nur fünf Jahre vergangen. Das Node.js Repository auf GitHub ist aktuell auf Platz zwei der „most starred repositories“2 (und unter den ersten 15 der „most forked repositories“3).

Auf der anderen Seite gibt es auch einen geradezu aggressiven „Backlash“4. Natürlich ist das „trolling“ ? sinnvolle Argumente sind in solchen Fällen nur selten auszumachen. Auch Beiträge wie „Node.JS Is Stupid And If You Use It So Are You“5 fallen ganz klar in diese Kategorie.

Warum ist das so? Node.js ist immer noch vergleichsweise neu, hat sich aber schon an vielen Stellen einen festen Platz in der Anwendungslandschaft ergattert. Nichtsdestotrotz gibt es aber immer noch eine Art missionarischen Effekt. Überzeugte Anwender möchten Node.js weiter aus seiner Nische holen und versuchen, andere von seinen Vorteilen zu überzeugen. Dann gibt es natürlich auch den gegenläufigen Effekt: Jemand probiert etwas aus, von dem er gerade Lobpreisungen über sich hat ergehen lassen. Und dann passt es nicht zu seinem Problem, seinen Erwartungen oder einfach seiner aktuellen Denkweise. Er hat Zeit investiert und ist enttäuscht. Also muss die Technologie nutzlos, unsinnig oder überflüssig sein. Unter Umständen reicht für viele Entwickler alleine schon die Tatsache, dass Node.js auf JavaScript basiert, um es als indiskutabel einzustufen.

Weshalb haben wir nun ein Buch über Node.js geschrieben? Nicht weil wir missionieren möchten und denken, dass Node.js das Beste seit geschnitten Brot ist. Es gibt Situationen, in denen Node.js eine gute Lösung darstellt, aber auch Situationen, in denen man vielleicht zu einer Alternative greifen sollte. In den nächsten Kapiteln versuchen wir nicht nur Vorteile, sondern auch Nachteile zu zeigen ? insbesondere auch im Hinblick auf „professionelle Softwareentwicklung“. JavaScript hat nicht den besten Ruf in unserer Industrie und es wird schnell behauptet, dass es nicht als Basis für unternehmenskritische Anwendungen dienen kann. Es ist natürlich schwierig, hier absolute K.O.-Kriterien für oder gegen eine solche Verwendung zu finden ? Node.js ist sicherlich erwachsen genug, um nicht sofort auszuscheiden. Wir versuchen aber, viele Aspekte zu betrachten, die in den Bereich der professionellen Softwareentwicklung fallen, und beleuchten, wie diese Aspekte in Node.js und JavaScript behandelt werden. Unserer Meinung nach schneiden Node.js und JavaScript hier nicht immer optimal ab. Allerdings hängt es vom konkreten Szenario ab, ob ein ganz bestimmter Aspekt nun benötigt wird oder nicht. Node.js hat es auf jeden Fall verdient, eine Chance zu bekommen. Es kann vielleicht nicht in allen Bereichen mit etablierten Sprachen und Umgebungen wie dem Java-Ökosystem mithalten, aber es gibt durchaus Situationen, in denen Node.js einfach auf der Überholspur vorbeizieht …

Also, was ist der Nutzen von Node.js? Oder besser: Welches Problem löst Node.js besser als andere Technologien?

Eines der wichtigsten Probleme für Internetanwendungen ist Skalierbarkeit. Wenn die Benutzeranzahl steigt, soll der Ressourcenverbrauch nach Möglichkeit linear steigen. Von den relevanten Ressourcen CPU, I/O und RAM soll Node.js vor allem die Skalierbarkeit bei I/O-intensiven Anwendungen verbessern.

Um dies zu erreichen, setzt Node.js vollständig auf asynchrone I/O-Zugriffe. Wann immer Node.js auf eine Datenbank, einen Webservice oder auf das Dateisystem zugreift, erfolgt der Aufruf asynchron. Was macht das für einen Unterschied?

Im synchronen Fall könnte ein Datenbankaufruf folgendermaßen aussehen:

result = database.query("SELECT * FROM user"); result.get…

Der aktuelle Thread wartet auf das Ergebnis und wird „geweckt“, wenn das Ergebnis vorliegt (s. Bild 1.1). Dieses Verfahren funktioniert gut und ist aus konzeptioneller Sicht leicht zu begreifen. Als Entwickler kann man so programmieren, als ob man keine Nebenläufigkeit hätte. Man muss sich (meist) nicht um Synchronisation kümmern. Und das Wichtigste: Die Kosten für das Wechseln von einem Thread zum nächsten sind vernachlässigbar.

Bild 1.1 multi-threaded Server

Doch was tun, wenn die Kosten für den Wechsel nicht mehr vernachlässigbar sind? Wenn auf den „Thread Context Switch“ ein signifikanter Anteil an der Gesamtlaufzeit entfällt?

Hinzu kommt, dass Threads Hauptspeicher verbrauchen. Ein Java-Applikationsserver hat oft 200 bis 300 Threads. Auf einem 64-Bit-Linux-System wird für einen Thread 1 MB für den Stack reserviert. Somit werden ca. 250 MB verbraucht, die nur für die Threads benötigt werden. In vielen Anwendungen spielt das keine Rolle, da für den Heap allein 8 GB veranschlagt werden. Doch wenn die Anwendung

  • sehr viele parallele Zugriffe hat,

  • viel I/O (Datenbank, Dateisystem etc.) benötigt,

wird es eng. Was heißt in diesem Zusammenhang „viel“? 100 Zugriffe pro Minute sind (unserer Meinung nach) noch nicht viel. Spätestens bei 100 parallelen Zugriffen pro Sekunde wird Node.js definitiv interessant.

Keine Threads!

Was macht Node.js anders? Node.js verwendet nur einen Thread. Das ist alles. Nun ja, das ist nun etwas plakativ formuliert und technisch auch nicht ganz korrekt, aber in einer ersten Näherung ist es das, was Node.js anders macht als die meisten gewohnten Umgebungen. Wie handhabt Node.js dann Hunderte von parallelen Zugriffen? Es arbeitet einfach einen Auftrag nach dem anderen ab ? und wann immer dann auf einen langsamen I/O-Zugriff gewartet werden muss, wird der aktuelle Auftrag einfach beendet und ein neuer Auftrag für die Verarbeitung der Antwort erzeugt.

Bild 1.2 Node.js Event-Loop

In der Abbildung „Node.js Event-Loop“ ist der Ablauf ? stark vereinfacht ? skizziert. Es gibt eine Warteschlange mit Aufträgen, die bereit zur Verarbeitung sind („ready to execute“). Sobald der Verarbeitungs-Thread frei ist, beginnt er mit der Abarbeitung des Auftrags „a“. Wenn dann jedoch ein I/O-Zugriff erfolgt, wird die Abarbeitung des Auftrags beendet und ein Folgeauftrag „a1“ erzeugt. Dieser Auftrag wird jedoch erst dann in die „ready-to-execute“-Queue eingestellt, wenn der I/O-Zugriff abgeschlossen ist.

Bei Node.js muss der Entwickler jeden Folgeauftrag als „Callback-Funktion“ selbst definieren ? ein Vorgehen, das jedem JavaScript-Entwickler sehr vertraut sein dürfte und zu Code wie dem folgenden führt:

database.query( "SELECT * FROM user", function(result) { result… } );

Wirklich schwer zu lesen wird es, wenn bei der Verarbeitung der Antwort wiederum ein I/O-Zugriff mit der nächsten Callback-Funktion zu schreiben ist. Statt der geschachtelten anonymen Funktionen kann man natürlich auch mit explizit benannten Funktionen arbeiten:

function schritt1(result) { ... database.query("...", schritt2); } function schritt2(result) { ... } database.query("...", schritt1);

Damit wird es jedoch schwerer, den tatsächlichen Ablauf in den Codestrukturen nachzuvollziehen.

Da es vielen Programmierern so geht, gibt es eine große Menge an Node.js-Bibliotheken, die einen besser lesbaren Code ermöglichen. Zwei solche Bibliotheken werden im zweiten Teil im Abschnitt 2.8 gezeigt.

Wenn es keine Threads gibt, stellt sich die Frage: Wie nutzt man Multi-Prozessor und Multi-Core-Maschinen sinnvoll aus? Wenn man Node.js auf einer solchen Maschine laufen lässt, wäre gerade mal ein Core ausgelastet. Deshalb sollte man pro Core eine Node.js-Instanz laufen lassen und noch einen Core für andere Aufgaben freihalten. Das bedeutet aber auch, dass man sich um das Thema Session State kümmern muss.

Kriterien für den Einsatz von Node.js

Durch die Vermeidung von Threads benötigt Node.js tendenziell weniger Hauptspeicher und durch den Wegfall des Thread-Context-Wechsels auch weniger CPU-Zyklen; was aber ? wie gesagt ? erst bei einer hohen Anzahl konkurrierender Zugriffe relevant wird.

Machen aber die Kosten für die Server einen relevanten Anteil am Budget...

 

© 2009-2024 ciando GmbH