Multi-Instance Node.js App i PaaS ved hjelp av Redis Pub / Sub

Hvis du valgte PaaS som vert for din søknad, har du sannsynligvis eller vil dette problemet: Appen din er distribuert til små "containere" (kjent som dynos i Heroku, eller tannhjul i OpenShift) og du vil skalere den. 

For å gjøre det øker du antall containere, og hver forekomst av appen din går ganske mye i en annen virtuell maskin. Dette er bra av flere grunner, men det betyr også at forekomstene ikke deler minnet. 

I denne opplæringen vil jeg vise deg hvordan du kan overvinne denne lille ulempen.

Når du valgte PaaS hosting, antar jeg at du hadde skalering i tankene. Kanskje nettstedet ditt allerede har sett Slashdot-effekten, eller du vil forberede deg på det. Uansett, det er ganske enkelt å få instansene til å kommunisere med hverandre.

Husk at i artikkelen vil jeg anta at du allerede har en Node.js-app skrevet og kjørt.


Trinn 1: Redis Setup

Først må du forberede Redis-databasen. Jeg liker å bruke Redis To Go, fordi oppsettet er veldig raskt, og hvis du bruker Heroku, er det et tillegg (selv om kontoen din må ha et kredittkort tildelt det). Det er også Redis Cloud, som inkluderer mer lagring og sikkerhetskopiering.

Derfra er Heroku-oppsettet ganske enkelt: Velg tillegget på siden Heroku Add-ons, og velg Redis Cloud eller Redis To Go, eller bruk en av følgende kommandoer (merk at den første er for Redis To Go , og den andre er for Redis Cloud):

$ heroku addons: legg til redistogo $ heroku addons: legg til rediscloud

Trinn 2: Konfigurere node_redis

På dette tidspunktet må vi legge til nødvendig nodemodul til package.json fil. Vi vil bruke den anbefalte node_redis-modulen. Legg denne linjen til din package.json fil, i avhengighetsdelen:

"node_redis": "0.11.x"

Hvis du vil, kan du også inkludere hiredis, et høyytelsesbibliotek skrevet i C, som node_redis vil bruke hvis den er tilgjengelig:

"hiredis": "0.1.x"

Avhengig av hvordan du opprettet Redis-databasen og hvilken PaaS-leverandør du bruker, vil tilkoblingsoppsettet se litt annerledes ut. Du trenger vert, havn, brukernavn, og passord for tilkoblingen din.

Heroku

Heroku lagrer alt i config-variablene som nettadresser. Du må trekke ut informasjonen du trenger fra dem ved hjelp av Node url modulen (config var for Redis To Go er process.env.REDISTOGO_URL og for redis cloud process.env.REDISCLOUD_URL). Denne koden går øverst på hovedprogramfilen din:

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

andre

Hvis du opprettet databasen for hånd, eller bruker en annen leverandør enn Heroku, bør du allerede ha tilkoblingsmuligheter og referanser, så bare bruk dem:

var redis = krever ('redis'); var klient = redis.createClient (YOUR_HOST, YOUR_PORT); client.auth (ditt_passord);

Deretter kan vi begynne å jobbe med kommunikasjon mellom forekomster.


Trinn 3: Sende og motta data

Det enkleste eksempelet vil bare sende informasjon til andre tilfeller du nettopp har startet. For eksempel kan du vise denne informasjonen i administrasjonspanelet.

Før vi gjør noe, opprett en annen tilkobling som heter client2. Jeg vil forklare hvorfor vi trenger det senere.

La oss starte med bare å sende meldingen som vi startet. Det er gjort ved hjelp av publisere() metode for klienten. Det tar to argumenter: Kanalen vi vil sende meldingen til, og meldingsteksten:

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

Det er alt du trenger for å sende meldingen. Vi kan lytte etter meldinger i budskap hendelsehandler (legg merke til at vi kaller dette på vår andre klient):

client2.on ('message', funksjon (kanal, melding) 

Tilbakeringingen er bestått de samme argumentene som vi sender til publisere() metode. La oss nå vise denne informasjonen i konsollen:

hvis ((kanal == 'forekomster') og (melding == 'start')) console.log ('Ny forekomst startet!'); );

Den siste tingen å gjøre er å faktisk abonnere på kanalen vi skal bruke:

client2.subscribe ( 'tilfeller');

Vi brukte to klienter for dette fordi når du ringer abonnere() På klienten blir tilkoblingen tilkoblet til abonnenten modus. Fra det tidspunktet er de eneste metodene du kan ringe på Redis-serveren ABONNERE og AVSLUTTE ABONNEMENTET. Så hvis vi er i abonnenten modus vi kan publisere() meldinger.

Hvis du vil, kan du også sende en melding når forekomsten slås av - du kan lytte til SIGTERM hendelse og send meldingen til samme kanal:

process.on ('SIGTERM', funksjon () client.publish ('forekomster', 'stopp'); process.exit ();); 

Å håndtere dette tilfellet i budskap handler legg til dette eller hvis der inne

ellers hvis ((kanal == 'forekomster') og (melding == 'stopp')) console.log ('Instans stoppet!');

Så ser det ut som dette etterpå:

client2.on ('message', funksjon (kanal, melding) if ((channel == 'instances') og (message == 'start')) console.log ('Ny forekomst startet!'); (kanal == 'forekomster') og (melding == 'stopp')) console.log ('Instans stoppet!'););

Merk at hvis du prøver på Windows, støtter den ikke SIGTERM signal.

For å teste det lokalt, start programmet et par ganger og se hva som skjer i konsollen. Hvis du vil teste avslutningsmeldingen, må du ikke utstede Ctrl + C kommandoen i terminalen-istedenfor, bruk drepe kommando. Merk at dette ikke støttes på Windows, så du kan ikke sjekke det.

Bruk først ps kommandoen for å sjekke hvilket id prosessen din har - rør den til grep for å gjøre det enklere:

$ ps -aux | grep your_apps_name 

Den andre kolonnen i utgangen er IDen du ser etter. Husk at det også vil være en linje for kommandoen du bare kjørte. Kjør nå drepe kommandoen bruker 15 for signalet - det er det SIGTERM:

$ drepe -15 PID

PID er din prosess-ID.


Eksempler på ekte verden

Nå som du vet hvordan du bruker Redis Pub / Sub-protokollen, kan du gå utover det enkle eksempelet som ble presentert tidligere. Her er noen brukssaker som kan være nyttige.

Express Sessions

Denne er ekstremt nyttig hvis du bruker Express.js som rammeverk. Hvis din søknad støtter brukerinnlogginger, eller stort sett alt som bruker økter, vil du sørge for at brukerens økter blir bevart, uansett om forekomsten starter på nytt, flyttes brukeren til et sted som håndteres av en annen, eller brukeren Byttes til en annen forekomst fordi den opprinnelige gikk ned.

Noen få ting å huske:

  • De gratis Redis-forekomstene vil ikke være tilstrekkelig: du trenger mer minne enn de 5 MB / 25 MB de gir.
  • Du trenger en annen tilkobling for dette.

Vi trenger Connect-Redis-modulen. Versjonen avhenger av hvilken versjon av Express du bruker. Denne er for Express 3.x:

"connect-redis": "1.4.7"

Og dette for Express 4.x:

"connect-redis": "2.x"

Opprett nå en annen Redis-forbindelse som heter client_sessions. Bruken av modulen avhenger igjen av Express-versjonen. For 3.x lager du RedisStore som dette:

var RedisStore = krever ('connect-redis') (uttrykk)

Og i 4.x må du passere ekspress-session som parameter:

var session = krever ('express-session'); var RedisStore = krever ('connect-redis') (økt);

Etter det er oppsettet det samme i begge versjoner:

app.use (økt (butikk: ny RedisStore (klient: client_sessions), hemmelig: 'din hemmelige streng'));

Som du ser, passerer vi vår Redis-klient som klient eiendommen til objektet passert til RedisStoreKonstruktør, og så passerer vi butikken til økt konstruktør.

Nå, hvis du starter appen din, logger deg på eller starter en økt og starter på nytt, vil økten din bli bevart. Det samme skjer når forekomsten er byttet til brukeren.

Utveksling av data med WebSockets

La oss si at du har en helt adskilt forekomst (arbeider dyno på Heroku) for å gjøre mer ressurs-spise arbeid som kompliserte beregninger, behandling av data i databasen, eller utveksling av mye data med en ekstern tjeneste. Du vil ha "vanlige" forekomster (og dermed brukerne) for å få vite resultatet av dette arbeidet når det er gjort.

Avhengig av om du vil at webinstansene skal sende data til arbeideren, trenger du en eller to tilkoblinger (la oss kalle dem client_sub og client_pub på arbeideren også). Du kan også gjenbruke en forbindelse som ikke abonnerer på noe (som den du bruker til Express-økter) i stedet for client_pub.

Nå når brukeren ønsker å utføre handlingen, publiserer du meldingen på kanalen som er reservert bare for denne brukeren og for denne spesifikke jobben:

// dette går inn i forespørselsbehandleren client_pub.publish ('JOB: USERID: JOBNAME: START', JSON.stringify (THEDATAYOUWANTTOSEND)); client_sub.subscribe ( 'JOB: USERID: JOBBNAVN: PROGRESS');

Selvfølgelig må du erstatte BRUKER-ID og JOBB NAVN med passende verdier. Du bør også ha budskap handler forberedt på client_sub forbindelse:

klient_sub.on ('melding', funksjon (kanal, melding) var USERID = channel.split (':') [1], hvis (melding == 'DONE') client_sub.unsubscribe (kanal); stikkontakter [USERID] .emit (kanal, melding););

Dette trekker ut BRUKER-ID fra kanalnavnet (så pass på at du ikke abonnerer på kanaler som ikke er relatert til brukerjobber på denne tilkoblingen), og sender meldingen til den aktuelle klienten. Avhengig av hvilket WebSocket-bibliotek du bruker, vil det være noen måte å få tilgang til en kontakt med sin ID.

Du lurer kanskje på hvordan arbeidstakerens forekomst kan abonnere på alle disse kanalene. Selvfølgelig vil du ikke bare gjøre noen løkker på alle mulige BRUKER-IDs og JOBB NAVNs. De psubscribe () Metoden aksepterer et mønster som argumentet, slik at det kan abonnere på alle JOBB:* kanaler:

// denne koden går til arbeidstakers forekomst // og du kaller det EN gang client_sub.psubscribe ('JOB: *')

Vanlige problemer

Det er noen problemer du kan støte på når du bruker Pub / Sub:

  • Tilkoblingen din til Redis-serveren blir nektet. Hvis dette skjer, må du sørge for at du har riktig tilkoblingsmuligheter og legitimasjon, og at det maksimale antall tilkoblinger ikke er nådd.
  • Dine meldinger blir ikke levert. Hvis dette skjer, må du kontrollere at du abonnerer på samme kanal du sender meldinger på (virker dumt, men noen ganger skjer). Pass også på at du legger til budskap handler før du ringer abonnere(), og det du ringer til abonnere() i ett tilfelle før du ringer publisere() på den andre.