App Node.js multi-istanza in PaaS utilizzando Redis Pub / Sub

Se hai scelto PaaS come hosting per la tua applicazione, probabilmente hai o avrai questo problema: la tua app è distribuita in piccoli "contenitori" (noti come dynos in Heroku, o ingranaggi in OpenShift) e vuoi ridimensionarlo. 

Per fare ciò, aumenti il ​​numero di contenitori e ogni istanza della tua app è praticamente in esecuzione in un'altra macchina virtuale. Questo è utile per una serie di motivi, ma significa anche che le istanze non condividono la memoria. 

In questo tutorial ti mostrerò come superare questo piccolo inconveniente.

Quando hai scelto l'hosting PaaS, presumo che tu avessi il ridimensionamento in mente. Forse il tuo sito ha già assistito all'effetto Slashdot o vuoi prepararti per questo. In entrambi i casi, far sì che le istanze comunichino tra loro è piuttosto semplice.

Tieni presente che nell'articolo supporrò che tu abbia già un'app Node.js scritta e funzionante.


Passaggio 1: installazione Redis

Innanzitutto, devi preparare il tuo database Redis. Mi piace utilizzare Redis To Go, perché la configurazione è molto veloce, e se usi Heroku c'è un componente aggiuntivo (anche se al tuo account deve essere assegnata una carta di credito). C'è anche Redis Cloud, che include più spazio di archiviazione e backup.

Da lì, la configurazione di Heroku è abbastanza semplice: seleziona il componente aggiuntivo nella pagina dei componenti aggiuntivi di Heroku e seleziona Redis Cloud o Redis To Go, oppure usa uno dei seguenti comandi (nota che il primo è per Redis To Go e il secondo è per Redis Cloud):

$ addons heroku: aggiungi redistogo $ addons heroku: aggiungi rediscloud

Passaggio 2: impostazione di node_redis

A questo punto, dobbiamo aggiungere il modulo Node richiesto a package.json file. Useremo il modulo node_redis raccomandato. Aggiungi questa linea al tuo package.json file, nella sezione delle dipendenze:

"node_redis": "0.11.x"

Se vuoi, puoi anche includere hiredis, una libreria ad alte prestazioni scritta in C, che node_redis userà se è disponibile:

"hiredis": "0.1.x"

A seconda di come è stato creato il database Redis e quale provider PaaS si utilizza, l'impostazione della connessione sarà leggermente diversa. Hai bisogno ospite, porta, nome utente, e parola d'ordine per la tua connessione.

Heroku

Heroku memorizza ogni cosa nelle variabili di configurazione come URL. Devi estrarre le informazioni che ti servono da loro usando il nodo url modulo (config var per Redis To Go è process.env.REDISTOGO_URL e per Redis Cloud process.env.REDISCLOUD_URL). Questo codice si trova nella parte superiore del file dell'applicazione principale:

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

Altri

Se hai creato il database a mano, o usi un provider diverso da Heroku, dovresti avere già le opzioni di connessione e le credenziali, quindi basta usarle:

var redis = require ('redis'); var client = redis.createClient (YOUR_HOST, YOUR_PORT); client.auth (your_password);

Successivamente, possiamo iniziare a lavorare sulla comunicazione tra le istanze.


Passaggio 3: invio e ricezione dei dati

L'esempio più semplice invierà semplicemente le informazioni ad altre istanze che hai appena iniziato. Ad esempio, puoi visualizzare queste informazioni nel pannello di amministrazione.

Prima di fare qualsiasi cosa, crea un'altra connessione chiamata Client2. Spiegherò perché ne abbiamo bisogno in seguito.

Iniziamo semplicemente inviando il messaggio che abbiamo iniziato. È fatto usando il pubblicare() metodo del cliente. Ci vogliono due argomenti: il canale a cui vogliamo inviare il messaggio e il testo del messaggio:

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

Questo è tutto ciò che serve per inviare il messaggio. Possiamo ascoltare i messaggi nel Messaggio gestore di eventi (nota che lo chiamiamo sul nostro secondo client):

client2.on ('messaggio', funzione (canale, messaggio) 

Il callback è passato gli stessi argomenti che passiamo al pubblicare() metodo. Ora mostriamo queste informazioni nella console:

if ((channel == 'instances') e (message == 'start')) console.log ('New instance started!'); );

L'ultima cosa da fare è iscriversi effettivamente al canale che useremo:

client2.subscribe ( 'casi');

Abbiamo usato due client per questo perché quando chiami sottoscrivi() sul client, la sua connessione viene commutata su abbonato modalità. Da quel punto, gli unici metodi che puoi chiamare sul server Redis sono SOTTOSCRIVI e ANNULLA L'ISCRIZIONE. Quindi se siamo nel abbonato modalità che possiamo pubblicare() messaggi.

Se lo desideri, puoi anche inviare un messaggio quando l'istanza viene chiusa, puoi ascoltare il SIGTERM evento e invia il messaggio allo stesso canale:

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

Per gestire quel caso nel Messaggio gestore di aggiungere questo altrimenti se lì dentro:

else if ((channel == 'instances') e (message == 'stop')) console.log ('Instance stopped!');

Quindi sembra questo in seguito:

client2.on ('messaggio', funzione (canale, messaggio) if ((canale == 'istanze') e (messaggio == 'inizio')) console.log ('Nuova istanza avviata!'); altrimenti se ( (canale == 'istanze') e (messaggio == 'stop')) console.log ('Istanza ferma!'););

Nota che se stai provando su Windows, non supporta il SIGTERM segnale.

Per testarlo localmente, avvia la tua app alcune volte e guarda cosa succede nella console. Se si desidera testare il messaggio di terminazione, non rilasciare il Ctrl + C comando nel terminale, invece, usa il uccidere comando. Si noti che questo non è supportato su Windows, quindi non è possibile controllarlo.

Innanzitutto, usa il ps comando per verificare quale id il tuo processo lo ha convogliato grep per renderlo più facile:

$ ps -aux | grep your_apps_name 

La seconda colonna dell'output è l'ID per cui stai cercando. Tieni presente che ci sarà anche una riga per il comando che hai appena eseguito. Ora esegui il uccidere comando usando 15 per il segnale-è SIGTERM:

$ kill -15 PID

PID è il tuo ID di processo.


Esempi del mondo reale

Ora che sai come usare il protocollo Redis Pub / Sub, puoi andare oltre il semplice esempio presentato in precedenza. Ecco alcuni casi d'uso che possono essere utili.

Sessioni Express

Questo è estremamente utile se si utilizza Express.js come framework. Se l'applicazione supporta gli accessi degli utenti, o praticamente qualsiasi cosa utilizzi le sessioni, ti consigliamo di assicurarti che le sessioni utente vengano mantenute, indipendentemente dal riavvio dell'istanza, l'utente si sposta in una posizione gestita da un'altra o l'utente viene passato a un'altra istanza perché quella originale è andata giù.

Alcune cose da ricordare:

  • Le istanze Redis gratuite non sono sufficienti: è necessaria più memoria rispetto ai 5 MB / 25 MB forniti.
  • Avrai bisogno di un'altra connessione per questo.

Avremo bisogno del modulo connect-redis. La versione dipende dalla versione di Express che stai utilizzando. Questo è per Express 3.x:

"connect-redis": "1.4.7"

E questo per Express 4.x:

"connect-redis": "2.x"

Ora crea un'altra connessione Redis chiamata client_sessions. L'uso del modulo dipende ancora dalla versione Express. Per 3.x si crea il RedisStore come questo:

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

E in 4.x devi passare il Express-session come parametro:

var session = require ('express-session'); var RedisStore = require ('connect-redis') (sessione);

Dopo che l'installazione è la stessa in entrambe le versioni:

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

Come puoi vedere, stiamo passando il nostro cliente Redis come il cliente proprietà dell'oggetto passato a RedisStoreCostruttore, e quindi passiamo il negozio al sessione costruttore.

Ora se avvii la tua app, esegui l'accesso, o avvia una sessione e riavvii l'istanza, la tua sessione verrà mantenuta. Lo stesso accade quando l'istanza viene cambiata per l'utente.

Scambio di dati con WebSockets

Supponiamo tu abbia un'istanza completamente separata (operaio dyno su Heroku) per svolgere più attività di consumo di risorse come calcoli complicati, elaborazione dei dati nel database o scambio di molti dati con un servizio esterno. Volete che le istanze "normali" (e quindi gli utenti) conoscano il risultato di questo lavoro una volta terminato.

A seconda che si desideri che le istanze Web inviino dati al lavoratore, sono necessarie una o due connessioni (chiamiamole client_sub e client_pub anche sull'operaio). Puoi anche riutilizzare qualsiasi connessione che non si abbona a nulla (come quella che usi per le sessioni Express) invece di client_pub.

Ora quando l'utente vuole eseguire l'azione, si pubblica il messaggio sul canale riservato solo a questo utente e per questo specifico lavoro:

// questo va nel gestore della richiesta client_pub.publish ('JOB: USERID: JOBNAME: START', JSON.stringify (THEDATAYOUWANTTOSEND)); client_sub.subscribe ( 'JOB: USERID: JOBNAME: PROGRESS');

Certo che dovrai sostituire ID UTENTE e NOME DEL LAVORO con valori appropriati. Dovresti anche avere il Messaggio gestore preparato per il client_sub connessione:

client_sub.on ('messaggio', funzione (canale, messaggio) var USERID = channel.split (':') [1]; if (messaggio == 'DONE') client_sub.unsubscribe (canale); socket [USERID] .emit (canale, messaggio););

Questo estrae il ID UTENTE dal nome del canale (quindi assicurati di non abbonarti ai canali non correlati ai lavori utente su questa connessione) e invia il messaggio al client appropriato. A seconda della libreria WebSocket che usi, ci sarà un modo per accedere a un socket tramite il suo ID.

Potresti chiederti come l'istanza worker può iscriversi a tutti questi canali. Certo, non vuoi solo fare alcuni loop su tutti i possibili ID UTENTEs e NOME DEL LAVOROS. Il psubscribe () il metodo accetta un pattern come argomento, quindi può iscriversi a tutti LAVORO:* canali:

// questo codice va all'istanza worker // e tu lo chiami ONCE client_sub.psubscribe ('JOB: *')

Problemi comuni

Ci sono alcuni problemi che potresti incontrare quando usi Pub / Sub:

  • La tua connessione al server Redis viene rifiutata. Se ciò accade, assicurati di fornire le opzioni e le credenziali di connessione corrette e che il numero massimo di connessioni non sia stato raggiunto.
  • I tuoi messaggi non vengono consegnati. Se ciò accade, controlla di essere iscritto allo stesso canale sul quale stai inviando messaggi (sembra sciocco, ma a volte accade). Assicurati inoltre di allegare il Messaggio gestore prima di chiamare sottoscrivi(), e tu chiami sottoscrivi() su un caso prima di chiamare pubblicare() dall'altra.