Aplicația multi-instanță Node.js în PaaS Utilizând Redis Pub / Sub

Dacă ați ales PaaS ca găzduire pentru aplicația dvs., ați avut probabil sau veți avea această problemă: aplicația dvs. este distribuită în mici "containere" (cunoscute sub numele de dynos în Heroku, sau unelte în OpenShift) și doriți să o scalați. 

Pentru a face acest lucru, creșteți numărul de containere - și fiecare instanță a aplicației dvs. se execută aproape într-o altă mașină virtuală. Acest lucru este bun din mai multe motive, dar înseamnă, de asemenea, că instanțele nu împart memoria. 

În acest tutorial vă voi arăta cum să depășiți acest mic neplăcut.

Când ați ales hostingul PaaS, presupun că ați avut în minte. Poate că site-ul dvs. a fost deja martorul efectului Slashdot sau doriți să vă pregătiți pentru el. Oricum, comunicarea dintre instanțe este una simplă.

Rețineți că în articol voi presupune că aveți deja o aplicație Node.js scrisă și rulată.


Pasul 1: Redenumiți configurarea

În primul rând, trebuie să vă pregătiți baza de date Redis. Îmi place să folosesc Redis To Go, deoarece setarea este foarte rapidă și dacă utilizați Heroku există un add-on (deși contul dvs. trebuie să aibă un card de credit atribuit acestuia). Există, de asemenea, Redis Cloud, care include mai mult spațiu de stocare și copii de rezervă.

De aici, instalarea Heroku este destul de ușoară: selectați add-on-ul de pe pagina de Suplimente Heroku și selectați Redis Cloud sau Redis To Go sau utilizați una din următoarele comenzi (rețineți că prima este pentru Redis To Go , iar al doilea este pentru Redis Cloud):

Addons pentru heroku: adaugă redistogo $ heroku addons: adaugă rediscloud

Pasul 2: Configurarea node_redis

În acest moment, trebuie să adăugăm modulul Nod necesar la package.json fişier. Vom folosi modulul node_redis recomandat. Adăugați această linie la dvs. package.json fișier, în secțiunea dependențe:

"node_redis": "0.11.x"

Dacă doriți, puteți include, de asemenea hiredis, o bibliotecă de înaltă performanță scrise în C, care node_redis va folosi dacă este disponibil:

"hiredis": "0.1.x"

În funcție de modul în care ați creat baza de date Redis și de furnizorul de servicii PaaS pe care îl utilizați, configurarea conexiunii va părea puțin diferită. Ai nevoie gazdă, port, nume de utilizator, și parola pentru conexiunea ta.

Heroku

Heroku stochează totul în variabilele config ca adrese URL. Trebuie să extrageți informațiile de care aveți nevoie folosind Nodurile URL-ul modul (config var pentru Redis To Go este process.env.REDISTOGO_URL și pentru Redis Cloud process.env.REDISCLOUD_URL). Acest cod merge în partea de sus a fișierului principal de aplicație:

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

Alții

Dacă ați creat baza de date manuală sau ați folosit un alt furnizor decât Heroku, ar trebui să aveți deja opțiunile de conectare și acreditările, deci pur și simplu să le utilizați:

var redis = cer ('redis'); var client = redis.createClient (YOUR_HOST, YOUR_PORT); client.auth (parola_ta);

După aceasta, putem începe să lucrăm la comunicarea dintre instanțe.


Pasul 3: Trimiterea și primirea datelor

Cel mai simplu exemplu va trimite doar informații altor instanțe pe care tocmai ați început. De exemplu, puteți afișa aceste informații în panoul de administrare.

Înainte de a face ceva, creați o altă conexiune numită Client2. Voi explica de ce avem nevoie mai târziu.

Să începem prin trimiterea mesajului pe care l-am început. Sa făcut folosind publica() metodă a clientului. Este nevoie de două argumente: canalul la care vrem să trimitem mesajul și textul mesajului:

client.publish ("instanțe", "începe"); 

Asta e tot ce ai nevoie pentru a trimite mesajul. Putem asculta mesaje în mesaj manipulator de evenimente (observați că îl numim pe cel de-al doilea client):

client2.on ('mesaj', funcție (canal, mesaj) 

Callback-ul este trecut pe aceleași argumente pe care le trecem la publica() metodă. Acum, să afișăm aceste informații în consola:

dacă ((canal == "instanțe") și (message == 'start')) console.log ("A început o nouă instanță!"); );

Ultimul lucru pe care trebuie să-l faceți este să vă abonați de fapt la canalul pe care îl vom folosi:

client2.subscribe ( 'cazuri');

Am folosit doi clienți pentru asta, deoarece când sunați Abonati-va() pe client, conexiunea sa este comutată pe abonat Mod. Din acest punct, singurele metode pe care le puteți apela pe serverul Redis sunt ABONATI-VA și UNSUBSCRIBE. Deci, dacă suntem în abonat modul în care putem publica() mesaje.

Dacă doriți, puteți trimite și un mesaj când instanța este oprită - puteți asculta mesajul SIGTERM și trimiteți mesajul la același canal:

process.on ('SIGTERM', funcția () client.publish ('instanțe', 'stop'); process.exit ();); 

Pentru a rezolva cazul respectiv în mesaj handler adaugă acest lucru altfel dacă acolo:

altfel dacă ((canal == 'instanțe') și (message == 'stop')) console.log ('instanța a fost oprită!');

Asa se intampla astfel:

client2.on ('mesaj', functie (canal, mesaj) if ((canal == 'instanta') si (message == 'start') console.log (canal == 'instanțe') și (message == 'stop')) console.log ('instanța a fost oprită!'););

Rețineți că, dacă testați pe Windows, nu suportă SIGTERM semnal.

Pentru a le testa local, porniți aplicația de câteva ori și vedeți ce se întâmplă în consola. Dacă doriți să testați mesajul de terminare, nu emiteți Ctrl + C comanda în terminal - în schimb, utilizați ucide comanda. Rețineți că acest lucru nu este acceptat pe Windows, deci nu îl puteți verifica.

Mai întâi, utilizați ps comanda pentru a verifica ce ID-ul dvs. proces-a-pipe-l grep a face mai ușor:

$ ps -aux | grep your_apps_name 

A doua coloană a ieșirii este ID-ul pentru care căutați. Rețineți că va exista și o linie pentru comanda pe care tocmai ați fugit-o. Acum executați ucide folosind comanda 15 pentru semnal - este SIGTERM:

$ ucide -15 PID

PID este ID-ul procesului.


Exemple din lumea reală

Acum că știți cum să utilizați protocolul Redis Pub / Sub, puteți trece dincolo de exemplul simplu prezentat mai devreme. Iată câteva cazuri de utilizare care pot fi utile.

Sesiuni rapide

Acest lucru este extrem de util dacă utilizați Express.js ca cadru. Dacă aplicația dvs. acceptă datele de conectare ale utilizatorilor sau ceva ce utilizează sesiunile, veți dori să vă asigurați că sesiunile de utilizatori sunt conservate, indiferent dacă instanța repornește, utilizatorul se deplasează într-o locație care este gestionată de un alt utilizator sau utilizatorul este schimbată într-o altă instanță, deoarece cea originală a căzut.

Cateva lucruri de retinut:

  • Instanțele gratuite Redis nu vor fi suficiente: aveți nevoie de mai multă memorie decât cele de 5MB / 25MB pe care le oferă.
  • Vei avea nevoie de o altă conexiune pentru asta.

Vom avea nevoie de modulul connect-redis. Versiunea depinde de versiunea Express pe care o utilizați. Aceasta este pentru Express 3.x:

"connect-redis": "1.4.7"

Și pentru Express 4.x:

"connect-redis": "2.x"

Acum creați o altă conexiune Redis numită client_sessions. Utilizarea modulului depinde din nou de versiunea Express. Pentru 3.x creați RedisStore asa:

var RedisStore = necesită ('connect-redis') (expres)

Și în 4.x trebuie să treci Express-sesiune ca parametru:

var sesiune = necesită ("sesiune expresă"); var RedisStore = necesită ('connect-redis') (sesiune);

După aceea, configurația este aceeași în ambele variante:

app.use (sesiune (store: new RedisStore (client: client_sessions), secret: "șirul tău secret"));

Dupa cum vedeti, noi trecem pe clientul nostru Redis ca client proprietatea obiectului trecut RedisStoreconstructor, apoi vom trece magazinul la sesiune constructor.

Acum, dacă începeți aplicația, conectați-vă sau inițiați o sesiune și reporniți instanța, sesiunea dvs. va fi păstrată. Același lucru se întâmplă atunci când instanța este schimbată pentru utilizator.

Schimbul de date cu WebSockets

Să presupunem că aveți o instanță complet separată (lucrător dyno pe Heroku) pentru a face mai multe lucrări cu resurse, cum ar fi calcule complicate, prelucrarea datelor în baza de date sau schimbul de date cu un serviciu extern. Veți dori instanțele "normale" (și, prin urmare, utilizatorii) să cunoască rezultatul acestei lucrări când este terminat.

În funcție de faptul dacă doriți ca instanțele Web să trimită lucrătorului date cu date, veți avea nevoie de una sau două conexiuni (să le numim client_sub și client_pub și pe muncitor). De asemenea, puteți reutiliza orice conexiune care nu se abonează la nimic (ca cel pe care îl utilizați pentru sesiunile Express) în loc de client_pub.

Acum când utilizatorul dorește să efectueze acțiunea, publicați mesajul pe canalul rezervat doar pentru acest utilizator și pentru această lucrare specifică:

// aceasta merge în solicitantul dvs. de solicitare client_pub.publish ("JOB: USERID: JOBNAME: START", JSON.stringify (THEDATAYOUWANTTOSEND)); client_sub.subscribe ( 'JOB: USERID: jobname: PROGRESS');

Desigur, va trebui să înlocuiți NUMELE DE UTILIZATOR și NUMELE LOCULUI DE MUNCA cu valori corespunzătoare. Ar trebui să aveți și mesaj manipulant pregătit pentru client_sub conexiune:

client_sub.on ('mesaj', funcție (canal, mesaj) var USERID = canal.split (':') [1] .emit (canal, mesaj););

Aceasta extrage NUMELE DE UTILIZATOR de la numele canalului (asigurați-vă că nu vă abonați la canalele care nu sunt legate de operațiile utilizatorului în această conexiune) și trimiteți mesajul clientului corespunzător. În funcție de biblioteca WebSocket pe care o utilizați, va exista o modalitate de a accesa un soclu prin codul său de identificare.

Vă puteți întreba cum se poate subscrie instanța lucrătorilor la toate aceste canale. Desigur, nu vrei doar să faci câteva bucle pe toate posibilurile NUMELE DE UTILIZATORși NUMELE LOCULUI DE MUNCAs. psubscribe () metoda acceptă un model ca argument, astfel încât să se poată abona la toate LOC DE MUNCA:* canale:

// acest cod merge la instanța muncitorului // și îl apelați la client_sub.psubscribe ('JOB: *')

Probleme comune

Există câteva probleme pe care le puteți întâlni când folosiți Pub / Sub:

  • Conexiunea dvs. la serverul Redis este refuzată. Dacă se întâmplă acest lucru, asigurați-vă că furnizați opțiunile de conectare adecvate și acreditările și că numărul maxim de conexiuni nu a fost atins.
  • Mesajele dvs. nu sunt livrate. Dacă se întâmplă acest lucru, verificați dacă v-ați abonat la același canal la care trimiteți mesaje (pare prostește, dar uneori se întâmplă). De asemenea, asigurați-vă că atașați mesaj handler înainte de a apela Abonati-va(), și pe care îl chemați Abonati-va() pe o singură instanță înainte de a apela publica() pe de altă parte.
Cod