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.
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
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 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]);
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.
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.
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.
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:
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 RedisStore
Costruttore, 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.
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 UTENTE
s e NOME DEL LAVORO
S. 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: *')
Ci sono alcuni problemi che potresti incontrare quando usi Pub / Sub:
Messaggio
gestore prima di chiamare sottoscrivi()
, e tu chiami sottoscrivi()
su un caso prima di chiamare pubblicare()
dall'altra.