Improved NodeJS Events durch Cluster …


… jeder von euch kennt das Problem, der Kollege kommt um die Ecke und möchte wenn möglich sofort, dass ein neues geiles Tools mit in den Infra Stack aufgenommen wird, gesagt getan.
In diesem Bespiel eine besonders cooles in NodeJS geschriebenes Dashboard für… lass es hier ein DockerManagement Tool sein. Dabei stößt man bei der großen Akzeptanz durch die Kollegen, auch auf einmal auf noch ganz andere Probleme (Stichwort: Performance Bottlenecks wie DDoS durch eigene Leute).
Hierbei könnte euch das NodeJS Modul Cluster behilflich sein, hier ist die Doku zu finden.
Cluster macht dabei nicht viel, es erweitert den NodeJS Stack insbesondere das Event Driven Modell von NodeJS ( so Arbeitet Google(s) v8 Engine ) um eine Art Interprocess Schnittstelle zu forked Child NodeJS Prozessen, die sich dabei Filesystem Elemente/Typen wie Sockets, Filedeskriptoren etc. teilen können und somit gemeinsam auf diesen operieren können.
Hier wollen wir eine schon bestehende Express Framework Anwendung mit einigen wenigen Zeilen Code um die Fähigkeit erweitern, dem System zur Verfügung stehenden CPU Cores effizienter ausnutzen zu können, damit sich die Events schneller abarbeiten lassen.
Ich werde hier allerdings nicht Express selber beschreiben, sondern setzte hier voraus das der Leser dieses Framework kennt. Wenn nicht könnt ihr die Infos unter diesem Link abrufen, so let’s beginn …

$ sudo -i                                  # <- da meine Workstation ein Ubuntu ist sollten wir zumindest was die Essentials sind kurz zu 'root' werden um diese Problemlos installiert zu bekommen
$ npm install express -g                   # <- installiert uns das Express Framework samt CLI Tools
express@4.13.3 /usr/lib/node_modules/express
├── escape-html@1.0.2
├── merge-descriptors@1.0.0
├── cookie@0.1.3
├── array-flatten@1.1.1
├── cookie-signature@1.0.6
├── utils-merge@1.0.0
├── content-type@1.0.1
├── fresh@0.3.0
├── path-to-regexp@0.1.7
├── content-disposition@0.5.0
├── vary@1.0.1
├── etag@1.7.0
├── serve-static@1.10.0
├── range-parser@1.0.2
├── methods@1.1.1
├── parseurl@1.3.0
├── depd@1.0.1
├── qs@4.0.0
├── on-finished@2.3.0 (ee-first@1.1.1)
├── finalhandler@0.4.0 (unpipe@1.0.0)
├── debug@2.2.0 (ms@0.7.1)
├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)
├── send@0.13.0 (destroy@1.0.3, statuses@1.2.1, ms@0.7.1, mime@1.3.4, http-errors@1.3.1)
├── type-is@1.6.9 (media-typer@0.3.0, mime-types@2.1.7)
└── accepts@1.2.13 (negotiator@0.5.3, mime-types@2.1.7)
$ npm install express-generator -g
/usr/bin/express -> /usr/lib/node_modules/express-generator/bin/express
express-generator@4.13.1 /usr/lib/node_modules/express-generator
├── sorted-object@1.0.0
├── commander@2.7.1 (graceful-readlink@1.0.1)
└── mkdirp@0.5.1 (minimist@0.0.8)
$ exit                                     # <- ab hier geht es ohne 'root' Rechte weiter
$ mkdir cool-app && cd cool-app            # <- Projekt Verzeichnis anlegen und in dieses wechseln
$ pwd                                      # <- vergewissern das wir uns auch in diesem wirklich befinden
/home/enzo/nodejsProjects/cool-app
$ express --git .                          # <- wir provisionieren uns unser Projekt from Scratch, dieses ist somit direkt Lauffähig ohne das wir einen Zeile Code schreiben müssen, haben hier leider keine Zeit zu 😉
   create : .
   create : ./package.json
   create : ./app.js
   create : ./.gitignore
   create : ./public
   create : ./routes
   create : ./routes/index.js
   create : ./routes/users.js
   create : ./views
   create : ./views/index.jade
   create : ./views/layout.jade
   create : ./views/error.jade
   create : ./bin
   create : ./bin/www
   create : ./public/javascripts
   create : ./public/images
   create : ./public/stylesheets
   create : ./public/stylesheets/style.css
   install dependencies:
     $ cd . && npm install
   run the app:
     $ DEBUG=cool-app:* npm start
$

Ab hier können wir das ganze mal kurz testen, um uns zu vergewissern das die Anwendung auch läuft ...

$ npm start
> cool-app@0.0.0 start /home/enzo/nodejsProjects/cool-app
> node ./bin/www

Nicht wundern die Applikation bleibt im Vordergrund hängen, was ist Ok ist, somit können wir diese schneller terminieren und relaunchen. Weiter geht es mit einem Test im Browser, die Anwendung lauscht standardmäßig am Port 3000, somit rufen wir hier einmal http://localhost:3000 auf und lassen uns überraschen was da so schönes kommt ...

Geil es läuft also, nun gut machen wir das ganze mit Cluster noch skalierbarer, let's go ...
Öffnet hierzu im Projekt Verzeichnis einmal die bin/www Datei, diese stellt laut package.json unseren Eintrittspunkt in die Anwendung dar, wir kopieren uns diese als Backup weg bevor wir anfangen hier Änderungen vorzunehmen.
Kommentiert bitte folgende Zeilen einmal aus ...

/**
 * Get port from environment and store in Express.
 */
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
 * Create HTTP server.
 */
var server = http.createServer(app);
/**
 * Listen on provided port, on all network interfaces.
 */
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

... nun fügt ihr ab Zeile 10 bitte folgendes hinzu ...

// cluster requirements
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
if( cluster.isMaster ) {
  for( var i = 0; i < numCPUs; i++ ) {
    cluster.fork();
  }
  cluster.on( 'listening', function( worker ){
    console.log( 'worker ' + worker.process.pid + ' is now listen for incoming connections ... ' );
  });
  cluster.on( 'exit', function( worker, code, signal ) {
    console.log( 'worker ' + worker.process.pid + ' died' );
  });
} else {
    // Workers can share any TCP connection
    // In this case it is an HTTP server
    /**
    * Get port from environment and store in Express.
    */
    var port = normalizePort(process.env.PORT || '3000');
    app.set('port', port);
    /**
    * Create HTTP server.
    */
    var server = http.createServer(app);
    /**
    * Listen on provided port, on all network interfaces.
    */
    server.listen(port);
    server.on('error', onError);
    server.on('listening', onListening);
}

.. jetzt sollte das ganze auch schon funktionieren, probieren wir es doch einmal aus.

$ npm start
> cool-app@0.0.0 start /home/enzo/nodejsProjects/cool-app
> node ./bin/www
worker 31979 is now listen for incoming connections ...
worker 31974 is now listen for incoming connections ...
worker 31985 is now listen for incoming connections ...
worker 31996 is now listen for incoming connections ...
GET / 304 840.784 ms - -
GET /stylesheets/style.css 304 31.416 ms - -

Jetzt könnte man denken das sich hier nichts geändert hat, dem ist aber nicht so, der Beweis ist in der Prozess Tabelle zu finden, gucken wir doch kurz einmal hinein ...

$ ps auxf | grep node
enzo     31969  0.1  0.4 901264 33004 pts/7    Sl+  14:47   0:00          |   |       \_ node ./bin/www
enzo     31974  0.1  0.4 901256 32324 pts/7    Sl+  14:47   0:00          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www
enzo     31979  0.4  0.6 946192 47560 pts/7    Sl+  14:47   0:01          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www
enzo     31985  0.1  0.4 902668 32736 pts/7    Sl+  14:47   0:00          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www
enzo     31996  0.1  0.4 901256 32168 pts/7    Sl+  14:47   0:00          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www

Whoop whoop, 4 neue NodeJS Prozesse die hier ihre Arbeit verrichten, somit lassen sich auch die beschäftigsten Web Anwendungen wieder beschleunigen.
Das ist aber nur der Anfang wie sich NodeJS Applications aller Art verbessern lassen, für weiter Themen habe ich heute allerdings keine Zeit daher heben wir uns den Rest einfach für ein anderes mal auf.
Ich wünsche euch hiermit noch viel Spaß, ich hoffe ihr habt nun mehr Lust auf NodeJS bekommen.

Gulp und Bower: Automate Webdevelopment

NETWAYS DevelopmentDas Entwickeln von HTML Templates ist normalerweise ein sehr statisches unterfangen. Man braucht nicht viel mehr als einen Texteditor und einen Browser um ein Design zu entwerfen. Integriert man allerdings Gedanken zur Produktion bereits in der Designphase, steigt der Aufwand wesentlich. Denn folgende Dinge gilt es zu bedenken:

  • Kompilieren von Stylesheets wenn in LESS oder SASS geschrieben
  • Aufteilen von Inline-Styles und nachladbare Styles
  • Verwenden von JavaScript Frameworks
  • Optimiertes laden von JavaScript Code
  • Komprimieren von Bildern
  • Komprimieren von Styles oder Scripts

Die Liste ist lässt sich entsprechend fortführen. Um beim Release nichts zu vergessen, etablierten sich in den letzten Jahren einige Tools um einen Buildprozess für clientseitige Webanwendungen bereitzustellen und zu vereinfachen. Zwei davon möchte ich gerne vorstellen:

Bower

Bower
Bower ist ein Open Source Paketverwaltungstool für Webapplikationen. Dadurch können für eine Website oder eine SPA (Single Page Application) softwaretechnische Abhängigkeiten definiert werden. Vorraussetzung zur Installation ist Node.js. Dann bietet Bower allerdings die Möglichkeit, Frameworks auch einfache Art und Weise in der benötigten Version zu installieren. Bekannte Vertreter dieser Art sind z.B. jQuery, AngualarJS oder HTML5 Boilerplate.
Ein einfaches bower.json sieht folgendermaßen aus:

{
  "name": "Website",
  "version": "0.0.9",
  "authors": [
    "Eduart Zimmermann <ez@website.foo.bar>"
  ],
  "description": "Our website",
  "keywords": [
    "website"
  ],
  "license": "GPLv2+",
  "homepage": "https://website.foo.bar",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "directory": "./src/scripts/vendor",
  "dependencies": {
    "jquery": "latest",
    "uikit": "latest",
    "modernizr": "2.8.x"
  },
  "devDependencies": {
    "html5-boilerplate": "latest"
  }
}

Sind die Abhängigkeiten definiert, werden sie mit diesem Aufruf in der Applikation installiert:

# bower install

Gulp

GulpGulp Logo ist nun das eigentlich Build Tool und wird ebenfalls mit Node.js (npm) installiert. Gulp selbst führt dabei nur weitere Tasks aus.
 
Diese Tasks übernehmen dann spezielle Aufgaben, z.B.:

  • Kompilieren, komprimieren, unleserlich machen von Javascript oder Stylesheet
  • Ausführen von Tasks, wenn sich Dateien geändert haben
  • Erstellen von Scripts und Styles aus verschiedenen Dateien
  • Ausführen von Tests

Die Tasks werden in einem gulpfile.js definiert und können ganze Gruppen ausführen (z.B. wie GNU Make) um Tarballs zu erstellen oder die Applikation allgemein für den Produktivbetrieb vorzubereiten. Das alleine haut uns nun nicht wirklich aus den Socken, aber: In Kombination mit Node.js und Verwendung aller hier genannten Techniken bauen wir uns mit 1-2 Anweisungen die komplette Entwicklungsumgebung auf:

  • Node Package Abhängigkeiten installieren
  • Bower Abhängigkeiten installieren
  • Webserver starten
  • Styles und JavaScript nur kompilieren nur wenn sich Dateien verändert haben
  • Per LifeReload im Browser die Seite neu laden wenn Code geändert worden ist

Damit kommt die Kiste dann richtig in Fahrt und Webentwicklung wird wieder zu einer spannenden und dynamischen Geschichte.
Weitere Informationen:

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.

Einfache asynchrone Aufrufe in JavaScript mit Async.js

JavaScript ist eine Programmiersprache die als Non-Blocking bezeichnet wird: Wenn ein Funktionsaufruf längere Zeit in Anspruch nimmt, wie das z.B. bei AJAX oder Timeouts der Fall ist, wird der Thread des Codes nicht schlafen gelegt bis der Funktionsaufruf beendet ist, sondern läuft sofort weiter. Es ist nicht möglich auf die Auswirkungen des Aufrufs zu warten, außer man benutzt eine weitere Funktion die dann als Callback aufgerufen wird.
In den Anfangstagen von JavaScript hatte dieses Vorgehen durchaus seinen Sinn: Da die Sprache nur für einfache Animationen gedacht war und kein Multithreading unterstützt hat (was sie übrigens auch heute nur mithilfe von WebWorkern beherrscht), wären sonst bei jedem Timeout die Animationen der Website stehen geblieben. Heute wird JavaScript sehr oft verwendet um per AJAX Inhalte nachzuladen. Komplexere Funktionalität erfordern dann aber immer öfter mehrfach ineinander verschachtelte Callbacks, was den Code schnell schwer durschaubar werden lässt.
Als Beispiel verwende ich nur setTimeout(), man kann sich an dieser Stelle aber auch beispielsweise einen HTTP-GET per AJAX oder eine beliebige andere asynchrone Funktion vorstellen. Je mehr Funktionen dazu kommen, desto verwirrender wird der Code:

setTimeout(function(){
   alert("Du bist jetzt seit einer Sekunde auf dieser Seite");
   setTimeout(function(){
       alert("Seit der letzten Meldung sind 10 Sekunden vergangen");
       setTimeout(function(){
           alert("Eingeschlafen?");
       }, 3600*1000);
   }, 10000);
}, 1000)

Natürlich könnte man stattdessen auch einfach eine Funktion schreiben, die eine Liste an Funktionen als Argumente annimmt, und diese dann alle in Serie aufruft. Einfacher geht es allerdings mit Async.js:

async.series([
    function(callback) {
        setTimeout(function() {
           alert("Du bist seit einer Sekunde auf dieser Seite");
           callback();
        }, 1000);
    },
    function(callback) {
        setTimeout(function() {
            alert("Seit der letzten Meldung sind 10 Sekunden vergangen.");
            callback();
        }, 10000);
    },
    function(callback) {
        setTimeout(function(callback) {
            alert("Eingeschlafen?");
            callback();
        }, 1000*3600)
    }
]);

Die Funktion series führt alle Funktionen der Reihe nach aus. Mithilfe des Callbacks wird Async.js mitgeteilt wenn die Funktion beendet ist. Dieser Code ist zwar länger, aber dafür wesentlich lesbarer: Es ist auf einen Blick zu erkennen, wie lange die einzelnen Schritte dauern und welcher Code dann ausgeführt wird. Auch bei sehr vielen voneinander abhängigen Aufrufen bleibt die Zahl der Einrückungen übersichtlich.
Dieses Beispiel zeigt nur den einfachsten Anwendungsfall von Async.js. Es gibt neben series noch viele andere Strategien um Aufrufe zu synchronisieren. Mit parallel werden die Funktionen nicht seriell, sondern parallel gestartet. Ein Callback wird aufgerufen sobald alle Funktionen fertig abgearbeitet wurden. Außerdem gibt es noch viele funktionale Strategien wie map, reduce und filter, die benutzt werden können um eine Collection aus Daten einfach mit asynchronen Funktionen zu verändern.

PNGs mit node.js


Wer den letzten Blogpost gelesen hat und sich auch fragt, was node.js ist, dem (und natürlich auch allen anderen ;-)) möchte ich eine kurze Einführung geben.
Mit JavaScript und HTML kann man viele tolle Sachen machen – zum Beispiel Zeichnen im Canvas-Element. Ohne Browser und eine Person, die diesen bedient, sieht man relativ wenig. Für Reports beispielsweise, steht außer Frage, dass Bildgenerierung automatisiert ablaufen muss.
Mit Node.js, einem serverseitigem JavaScript-Framework zum Erstellen von Server- und Konsolenanwendungen, kann nicht nur diese Aufgabe bewältigt werden.
Auf dem Vormarsch
Node.js ist mittlerweile 2 Jahre “alt” – Updates, zumindest in der Unstable-Linie, erscheinen regelmäßig. Sogar Microsoft beteiligt sich an der Entwicklung. Die Zahlen von GitHub und deren Mailing List lassen erkennen, dass sich node.js an einer wachsenden Community und reger Entwicklung erfreut.
Die letzte Stabile Version (0.6, November 2011) hat leider noch keinen Einzug in die Repositories der bekannten Linux-Distributionen gefunden. Die aktuelle Version für Fedora und Enterprise-Distributionen kann hier bezogen werden. Für Debian und Ubuntu ist dieses PPA zu empfehlen.
Auch wir setzen auf node.js, zum Beispiel bei inGraph.
Unter der Haube
Basis von node.js ist die schnelle JavaScript-Engine V8 von Google, die den JavaScript-Code vor dem Ausführen in Maschinencode übersetzt.
Modular erweiterbar und mit schlankem Kern, kommt es ohne weitere externe Abhängigkeiten aus – nur Python >= 2.5 wird benötigt.
Besonderheit von node.js ist die ereignisgesteuerte Architektur. Standardmäßig blockieren Ein- und Ausgabeoperationen (zum Beispiel Datenbankzugriffe) den Programmablauf. Im Gegensatz zur herkömmlichen Methode (Threading) und dem damit verbundenen Umgang mit Thread-Sicherheit (Locking, Race Conditions), setzt es auf “non-blocking I/O”.
Das heißt, dass alle Funktionen, die in anderen Umgebungen blockieren würden, Rückruffunktionen (Callback) als Parameter erwarten, die erst dann aufgerufen werden, wenn die Operation im Hintergrund ausgeführt wurde.
Los geht’s
Die Installation gestaltet sich recht einfach

# wget http://nodejs.org/dist/v0.6.8/node-v0.6.8.tar.gz
# tar xzf node-v0.6.8.tar.gz
# cd node-v0.6.8
# ./configure
# make
# make install

Neben node, wurde auch der Node Package Manager installiert. Den benutzen wir, um das benötigte Modul node-canvas zu installieren

# npm install node-canvas

Ein Rechteck
Folgender Code zeigt beispielhaft die Benutzung des Core-Moduls fs und des kürzlich installierten canvas


var Canvas = require('canvas'),
    canvas = new Canvas(150, 150),
    ctx = canvas.getContext('2d'),
    fs = require('fs'); // File I/O
ctx.fillStyle = '#00F';
ctx.fillRect(0, 0, 150, 150);
var out = fs.createWriteStream(__dirname + '/rectangle.png'),
    stream = canvas.createPNGStream();
stream.on('data', function(data){
  out.write(data);
});

Rufen wir das Skript nun mit dem node-Binary auf, so erstellt es uns ein blaues Rechteck mit dem Namen rectangle.png

# node png.js

Weitere Informationen findet man auf der Website der Entwickler.

Eric Lippmann
Eric Lippmann
Lead Senior Developer

Eric kam während seines ersten Lehrjahres zu NETWAYS und hat seine Ausbildung bereits 2011 sehr erfolgreich abgeschlossen. Seit Beginn arbeitet er in der Softwareentwicklung und dort an den unterschiedlichen NETWAYS Open Source Lösungen, insbesondere inGraph und im Icinga Team an Icinga Web. Darüber hinaus zeichnet er sich für viele Kundenentwicklungen in der Finanz- und Automobilbranche verantwortlich.