Multi-Instance Node.js-App in PaaS mit Redis Pub / Sub

Wenn Sie PaaS als Hosting für Ihre Anwendung ausgewählt haben, hatten oder haben Sie wahrscheinlich dieses Problem: Ihre App wird in kleinen "Containern" (bekannt als Dynos in Heroku oder Getriebe in OpenShift) und Sie möchten es skalieren. 

Dazu erhöhen Sie die Anzahl der Container - und jede Instanz Ihrer App wird praktisch auf einer anderen virtuellen Maschine ausgeführt. Dies ist aus mehreren Gründen sinnvoll, bedeutet aber auch, dass die Instanzen keinen Speicher gemeinsam nutzen. 

In diesem Tutorial zeige ich Ihnen, wie Sie diese kleine Unannehmlichkeit überwinden können.

Wenn Sie sich für PaaS-Hosting entschieden haben, gehe ich davon aus, dass Sie Skalierung im Auge hatten. Vielleicht hat Ihre Website bereits den Slashdot-Effekt miterlebt oder möchten Sie sich darauf vorbereiten. In jedem Fall ist es ziemlich einfach, die Instanzen miteinander zu kommunizieren.

Denken Sie daran, dass ich in diesem Artikel davon ausgehen muss, dass Sie bereits eine Node.js-App geschrieben und ausgeführt haben.


Schritt 1: Redis Setup

Zuerst müssen Sie Ihre Redis-Datenbank vorbereiten. Ich benutze gerne Redis To Go, weil das Setup sehr schnell ist und wenn Sie Heroku verwenden, gibt es ein Add-On (obwohl Ihrem Konto eine Kreditkarte zugewiesen sein muss). Es gibt auch Redis Cloud, die mehr Speicher und Backups enthält.

Von dort ist das Heroku-Setup ziemlich einfach: Wählen Sie das Add-On auf der Heroku-Add-On-Seite aus und wählen Sie Redis Cloud oder Redis To Go aus, oder verwenden Sie einen der folgenden Befehle (beachten Sie, dass der erste Befehl für Redis To Go ist und die zweite ist für Redis Cloud):

$ heroku addons: add redistogo $ heroku addons: add rediscloud

Schritt 2: node_redis einrichten

An dieser Stelle müssen wir dem Knoten das erforderliche Knotenmodul hinzufügen package.json Datei. Wir werden das empfohlene node_redis-Modul verwenden. Fügen Sie diese Zeile Ihrem hinzu package.json Datei im Abschnitt Abhängigkeiten:

"node_redis": "0.11.x"

Wenn Sie möchten, können Sie auch hinzufügen Hiredis, eine in C geschriebene Hochleistungsbibliothek, die node_redis wird verwenden, wenn es verfügbar ist:

"Hiredis": "0,1.x"

Je nachdem, wie Sie Ihre Redis-Datenbank erstellt haben und welchen PaaS-Provider Sie verwenden, sieht der Verbindungsaufbau etwas anders aus. Du brauchst Wirt, Hafen, Nutzername, und Passwort für deine Verbindung.

Heroku

Heroku speichert alles in den Konfigurationsvariablen als URLs. Sie müssen die Informationen, die Sie benötigen, mit Hilfe von Nodes extrahieren url Modul (config var für Redis To Go ist process.env.REDISTOGO_URL und für Redis Cloud process.env.REDISCLOUD_URL). Dieser Code steht oben in Ihrer Hauptanwendungsdatei:

var redis = erfordern ('redis'); var url = erfordern ('url'); var redisURL = url.parse (YOUR_CONFIG_VAR_HERE); var client = redis.createClient (redisURL.host, redisURL.port); client.auth (redisURL.auth.split (':') [1]); 

Andere

Wenn Sie die Datenbank von Hand erstellt haben oder einen anderen Anbieter als Heroku verwenden, sollten Sie die Verbindungsoptionen und Anmeldeinformationen bereits haben. Verwenden Sie sie also einfach:

var redis = erfordern ('redis'); var client = redis.createClient (YOUR_HOST, YOUR_PORT); client.auth (YOUR_PASSWORD);

Danach können wir mit der Kommunikation zwischen Instanzen beginnen.


Schritt 3: Senden und Empfangen von Daten

Das einfachste Beispiel sendet nur Informationen an andere Instanzen, die Sie gerade gestartet haben. Sie können diese Informationen beispielsweise im Administrationsbereich anzeigen.

Bevor wir etwas tun, erstellen Sie eine weitere Verbindung mit dem Namen client2. Ich werde später erklären, warum wir es brauchen.

Beginnen wir mit dem Senden der von uns gestarteten Nachricht. Es ist fertig mit der veröffentlichen() Methode des Kunden. Es sind zwei Argumente erforderlich: der Kanal, an den die Nachricht gesendet werden soll, und der Nachrichtentext:

client.publish ('instanzen', 'start'); 

Mehr brauchen Sie nicht, um die Nachricht zu senden. Wir können auf Nachrichten im Internet hören Botschaft Eventhandler (Beachten Sie, dass wir dies bei unserem zweiten Client aufrufen):

client2.on ('message', Funktion (Kanal, Nachricht) 

Dem Callback werden die gleichen Argumente übergeben, die wir an das übergeben veröffentlichen() Methode. Lassen Sie uns nun diese Informationen in der Konsole anzeigen:

if ((channel == 'instance') und (message == 'start')) console.log ('Neue Instanz gestartet!'); );

Als letztes müssen Sie tatsächlich den Kanal abonnieren, den wir verwenden werden:

client2.subscribe ('Instanzen');

Wir haben zwei Clients dafür verwendet, weil Sie anrufen abonnieren() auf dem Client wird seine Verbindung auf die umgestellt Teilnehmer Modus. Ab diesem Zeitpunkt können Sie nur noch die Methoden auf dem Redis-Server aufrufen ABONNIEREN und ABMELDEN. Also wenn wir im sind Teilnehmer Modus können wir veröffentlichen() Mitteilungen.

Wenn Sie möchten, können Sie auch eine Nachricht senden, wenn die Instanz heruntergefahren wird SIGTERM Ereignis und senden Sie die Nachricht an den gleichen Kanal:

process.on ('SIGTERM', function () client.publish ('instance', 'stop'); process.exit ();); 

Um diesen Fall in der Botschaft Handler fügen Sie dies hinzu sonst wenn da drin:

sonst if ((channel == 'instance') und (message == 'stop')) console.log ('Instanz gestoppt!');

So sieht es danach aus:

client2.on ('message', Funktion (Kanal, Nachricht) if ((channel == 'instance') und (message == 'start')) console.log ('Neue Instanz gestartet!'); else if ( (channel == 'instance') und (message == 'stop')) console.log ('Instanz gestoppt!'););

Wenn Sie unter Windows testen, wird das nicht unterstützt SIGTERM Signal.

Um es lokal zu testen, starten Sie Ihre App ein paar Mal und sehen Sie, was in der Konsole passiert. Wenn Sie die Beendigungsnachricht testen möchten, geben Sie das nicht aus Strg + C Befehl im Terminal-stattdessen verwenden Sie die töten Befehl. Beachten Sie, dass dies unter Windows nicht unterstützt wird. Sie können es also nicht überprüfen.

Verwenden Sie zuerst die ps Befehl, um zu überprüfen, an welche ID Ihr Prozess es übergeben hat grep zu erleichtern:

$ ps -aux | grep your_apps_name 

Die zweite Spalte der Ausgabe ist die ID, nach der Sie suchen. Denken Sie daran, dass es auch eine Zeile für den Befehl gibt, den Sie gerade ausgeführt haben. Nun führe das aus töten Befehl mit fünfzehn für das Signal ist es SIGTERM:

$ kill -15 PID

PID ist Ihre Prozess-ID.


Praxisbeispiele

Nun, da Sie wissen, wie das Redis Pub / Sub-Protokoll verwendet wird, können Sie über das zuvor beschriebene einfache Beispiel hinausgehen. Hier sind einige Anwendungsfälle, die hilfreich sein können.

Express-Sitzungen

Dies ist äußerst hilfreich, wenn Sie Express.js als Framework verwenden. Wenn Ihre Anwendung Benutzeranmeldungen unterstützt oder praktisch alle Sitzungen, die Sitzungen verwenden, sollten Sie sicherstellen, dass die Benutzersitzungen beibehalten werden. Unabhängig davon, ob die Instanz neu gestartet wird, wechselt der Benutzer an einen Ort, der von einem anderen Benutzer oder dem Benutzer verwaltet wird wird auf eine andere Instanz umgeschaltet, da die ursprüngliche Instanz ausgefallen ist.

Ein paar Dinge zu beachten:

  • Die freien Redis-Instanzen reichen nicht aus: Sie benötigen mehr Speicher als die 5 MB / 25 MB, die sie bereitstellen.
  • Sie benötigen dafür eine andere Verbindung.

Wir benötigen das Connect-Redis-Modul. Die Version hängt von der verwendeten Express-Version ab. Dieser ist für Express 3.x:

"connect-redis": "1.4.7"

Und das für Express 4.x:

"connect-redis": "2.x"

Erstellen Sie nun eine weitere Redis-Verbindung mit dem Namen client_sessions. Die Verwendung des Moduls hängt wiederum von der Express-Version ab. Für 3.x erstellen Sie die RedisStore so was:

var RedisStore = required ('connect-redis') (express)

Und in 4.x muss man das bestehen Express-Sitzung als Parameter:

var session = required ('express-session'); var RedisStore = required ('connect-redis') (Sitzung);

Danach ist das Setup in beiden Versionen gleich:

app.use (session (store: new RedisStore (client: client_sessions), secret: 'your secret string'));

Wie Sie sehen, übergeben wir unseren Redis-Kunden als Klient Eigenschaft des Objekts, an das übergeben wurde RedisStore's Konstruktor, und dann übergeben wir den Laden an die Session Konstrukteur.

Wenn Sie jetzt Ihre App starten, sich anmelden oder eine Sitzung initiieren und die Instanz neu starten, bleibt Ihre Sitzung erhalten. Dasselbe passiert, wenn die Instanz für den Benutzer gewechselt wird.

Datenaustausch mit WebSockets

Nehmen wir an, Sie haben eine vollständig getrennte Instanz (Arbeiter-Dyno zu Heroku), um ressourcenfressende Arbeiten wie komplizierte Berechnungen durchzuführen, Daten in der Datenbank zu verarbeiten oder eine Menge Daten mit einem externen Dienst auszutauschen. Sie möchten, dass die "normalen" Instanzen (und damit die Benutzer) das Ergebnis dieser Arbeit erfahren, wenn sie fertig sind.

Abhängig davon, ob die Webinstanzen Daten an den Worker senden sollen, benötigen Sie eine oder zwei Verbindungen (nennen wir sie.) client_sub und client_pub auf dem Arbeiter auch). Sie können auch jede Verbindung wiederverwenden, die nichts abonniert (wie die, die Sie für Express-Sitzungen verwenden) anstelle der client_pub.

Wenn der Benutzer die Aktion ausführen möchte, veröffentlichen Sie die Nachricht in dem Kanal, der nur für diesen Benutzer und für diesen bestimmten Job reserviert ist:

// Dies geht in Ihren Request-Handler ein client_pub.publish ('JOB: USERID: JOBNAME: START', JSON.stringify (THEDATAYOUWANTTOSEND)); client_sub.subscribe ('JOB: USERID: JOBNAME: PROGRESS');

Natürlich musst du ersetzen BENUTZERIDENTIFIKATION und BERUFSBEZEICHNUNG mit entsprechenden Werten. Sie sollten auch die haben Botschaft Handler bereit für die client_sub Verbindung:

client_on ('message', Funktion (Kanal, Nachricht) var USERID = channel.split (':') [1]; if (message == 'DONE') client_sub.unsubscribe (channel); sockets [USERID] .emit (Kanal, Nachricht););

Dies extrahiert das BENUTZERIDENTIFIKATION aus dem Kanalnamen (stellen Sie sicher, dass Sie keine Kanäle abonnieren, die nicht mit Benutzeraufträgen für diese Verbindung zusammenhängen) und senden Sie die Nachricht an den entsprechenden Client. Je nachdem, welche WebSocket-Bibliothek Sie verwenden, gibt es eine Möglichkeit, auf einen Socket über seine ID zuzugreifen.

Sie fragen sich vielleicht, wie die Worker-Instanz all diese Kanäle abonnieren kann. Natürlich möchten Sie nicht nur ein paar Loops auf allen möglichen Wegen machen BENUTZERIDENTIFIKATIONs und BERUFSBEZEICHNUNGs. Das psubscribe () Die Methode akzeptiert ein Muster als Argument, sodass alle abonniert werden können JOB:* Kanäle:

// Dieser Code geht an die Worker-Instanz // und Sie nennen ihn ONCE client_sub.psubscribe ('JOB: *').

Allgemeine Probleme

Bei der Verwendung von Pub / Sub können einige Probleme auftreten:

  • Ihre Verbindung zum Redis-Server wird abgelehnt. Stellen Sie in diesem Fall sicher, dass Sie die richtigen Verbindungsoptionen und Anmeldeinformationen angeben und dass die maximale Anzahl an Verbindungen nicht erreicht wurde.
  • Ihre Nachrichten werden nicht zugestellt. Wenn dies der Fall ist, prüfen Sie, ob Sie denselben Kanal abonniert haben, auf dem Sie Nachrichten senden (scheint dumm zu sein, kommt jedoch manchmal vor). Vergewissern Sie sich auch, dass Sie die Botschaft Handler vor dem Aufruf abonnieren(), und dass du anrufst abonnieren() einmal, bevor Sie anrufen veröffentlichen() auf dem anderen.