Node.js ist ein serverseitiges Framework zum Erstellen von Server- und Konsolenanwendungen mit JavaScript. ADACOR verwendet die junge Technologie für einen Reverse High-Performance Proxy Cloudbase.
Node.js wird eingesetzt, um eine rein eventbasierte und nicht-blockende Infrastruktur zu erhalten. Es baut auf der JavaScript Engine V8 von Google auf, die auch im Crome-Browser zum Einnsatz kommt. Damit lassen sich auch im Backend Programme schreiben, die stark auf Parallelität ausgelegt sind.
Technologien im Test
Wir suchten nach der passenden Technologie für ein geplantes Projek. Da wir überprüfen wollten, welche Technologie bzw. Sprache unseren Ansprüchen am besten gerecht wird, haben wir mehrere Prototypen in verschiedenen Programmiersprachen programmiert. Da wir aus der PHP-Welt kommen und dort die größte Erfahrung vorweisen, wurden mit PHP zwei der Prototypen gebaut. Einmal ganz klassisch in einer Fast-CGI Umgebung und einmal HipHop-Kompiliert.
Beide Versuche brachten weder die benötigte Performance noch Parallelität, was wir vorab bereits erwartet hatten. Einen Prototypen bauten wir auch ganz klassisch mit C++. Von der Performance her gibt es bei einer sauberen Umsetzung nichts zu beanstanden. Da selbst ein geübter C++ Entwickler für die meisten Aufgaben die doppelte Entwicklungszeit als in anderen Sprachen benötigt, verwarfen wir aufgrund des nicht unerheblichen Aufwands diese Idee bald wieder.
Der Prototyp, der in JavaScript geschrieben wurde und auf node.js setzt, übertraf unsere Erwartungen, noch bevor wir uns tiefgründige Gedanken über Optimierungen machten. Bei ersten Lasttests konnten wir eine sehr hohe Anzahl an parallelen Verbindungen abarbeiten, ohne dass es auch nur annähernd zu Engpässen bei der Performance kam. Weitere Fakten, die die Entscheidung für diese Technologie beeinflussten, sind die extrem starke und aktive Community rund um node.js, sowie bereits gesammelte Erfahrung der Entwickler bei ADACOR mit JavaScript.
NPM (Node Package Manager) vereinfacht die Arbeit
Um nicht bei Null zu beginnen, bietet die node.js Community den NPM an, über den man auf einfache Art und Weise auf ein großes Verzeichnis an Modulen zugreifen kann. Hier findet man z.B. eine Basisversion eines HTTP Proxies, Tools um Konfigurationsdateien auszulesen, eine Bibliothek um asynchron Aufgaben zu erledigen, Konnektoren zu aktuellen SQL und NoSQL Datenbanken, sowie Bibliotheken wie underscore.js, was uns die Arbeit massiv vereinfachte.
Beispielcode für einen funktionierenden Proxy mit node.js
Um zu verdeutlichen, wie „einfach“ und schnell man mit node.js zu Ergebnissen gelangt, ist hier der beispielhaft der Code für einenfunktionierenden Proxy, ohne weitere Features.
var http = require(‚http‘),
httpProxy = require(‚http-proxy‘);
// Create your proxy server
httpProxy.createServer(9000,’localhost‘).listen(8000);
// Create your target server
http.createServer(function(req, res){
res.writeHead(200,{‚Content-Type‘:’text/plain‘});
res.write(‚request successfully proxied!’+’\n‘
+ JSON.stringify(req.headers,true,2));
res.end();
}).listen(9000);
Innerhalb von acht Zeilen wird ein Webserver gestartet, welcher auf Port 9000 lauscht und eine lesbare Darstellung der gesendeten Headers ausgibt. Auf Port 8000 lauscht der Proxyserver, der alle Anfragen, an den Webserver weiterleitet. Genutzt werden hier zu lediglich zwei Module, „http“, welches direkt im Code von node.js mitgeliefert wird und „http-proxy“, welches mittels NPM bezogen wurde.
Parallelität
Nach den ersten Gehversuchen und Lasttests haben wir festgestellt, dass nur ein Kern der CPU belastet wurde. Eine kurze Recherche ergab, dass der Node.js Prozess an sich in nur einem Thread läuft und man sich noch um die Verteilung kümmern muss. Das klingt erstmal umständlich. Allerdings bringt node.js das „Cluster“ Modul mit, welches einfachstes „Forking“ erlaubt. Ein paar Zeilen Code und wir haben einen Thread pro Kern gestartet. Jeder dieser Threads öffnet einen Socket, um Verbindungen entgegenzunehmen und node.js kümmert sich darum, die eingehenden Verbindungsversuche dem Thread zuzuweisen, der gerade am wenigsten Last verursacht.
var cluster = require(
'cluster');
var numCPUs = require('os').cpus().length;
var proxy = require('./proxy');
function Cluster() {
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
proxy.createServer();
}
}
Non-Blocking I/O
Erwähnenswert ist noch die Art und Weise, um auf Ressourcen zuzugreifen. Im folgenden Beispiel wird eine Textdatei gelesen und auf die Konsole ausgegeben. Normalerweise würde man hier die Datei öffnen, auf die erste Zeile warten und dann erst weitermachen, da aber alles asynchron und non-blocking stattfindet, kann der übrige Programmcode weiterlaufen, während die Datei gelesen wird. Sobald die Datei fertig gelesen wurde, wird der Callback aufgerufen.
fs
.readFile('/path/to/file',function
(
err
,data
){
if
(
err
)throw
err
;
console
.log(data
);
});
Ausblick
Die V8 Engine, die aktuell noch hinter node.js steht, basiert auf ECMAScript in Version 5. Die node.js Entwickler haben schon in Aussicht gestellt, dass sie möglichst schnell auf Version 6 wechseln, welches aktuell kurz vor der Veröffentlichung steht. In der neusten Version stehen dann einige Funktionalitäten zur Verfügung, die in anderen Sprachen schon lange selbstverständlich sind, aber bisher schlichtweg fehlten.
Im Detail sind das Konstanten, Defaultwerte für Parameter von Funktionen, Maps und Sets, Generatoren und Iteratoren, ein natives Modul- sowie Klassensystem und einige logische Bugfixe. Einige Features sind bereits seit Ende 2012 in den aktuellen Firefoxversionen integriert.
Wir Entwickler von ADACOR freuen uns auf ein interessantes Projekt mit aktuellen und neuen Technologien und sind gespannt, wie es mit node.js weitergeht. Wobei letzteres noch lange keinen finalen Status erreicht hat und aktuell in Version 0.8.16 vorliegt.Laut der letzten Changelogs auf blog.nodejs.org wird an der Performance-Steigerung am intensivsten weiterentwickelt.