Om du valde PaaS som värd för din ansökan har du förmodligen eller kommer att få detta problem: Din app är distribuerad till små "behållare" (känd som dynos i Heroku, eller kugghjul i OpenShift) och du vill skala den.
För att göra så ökar du antalet behållare, och varje instans av din app körs ganska mycket i en annan virtuell maskin. Det här är bra av ett antal skäl, men det betyder också att instanserna inte delar minnet.
I denna handledning kommer jag att visa dig hur du kan övervinna detta lilla besvär.
När du valde PaaS-värd antar jag att du hade skalning i åtanke. Kanske har din webbplats redan sett Slashdot-effekten eller du vill förbereda dig för det. Hur som helst, vilket gör instanser att kommunicera med varandra är ganska enkelt.
Tänk på att i artikeln antar jag att du redan har en Node.js-app som skrivs och körs.
Först måste du förbereda din Redis-databas. Jag gillar att använda Redis To Go, eftersom installationen är väldigt snabb, och om du använder Heroku finns det ett tillägg (även om ditt konto måste ha ett kreditkort tilldelat det). Det finns också Redis Cloud, som innehåller mer lagring och säkerhetskopiering.
Därifrån är Heroku-installationen ganska enkel: Välj tillägget på sidan Heroku tillägg och välj Redis Cloud eller Redis To Go, eller använd en av följande kommandon (observera att den första är för Redis To Go , och den andra är för Redis Cloud):
$ heroku addons: lägg till redistogo $ heroku addons: lägg till rediscloud
Vid denna tidpunkt måste vi lägga till den obligatoriska nodmodulen till package.json
fil. Vi använder den rekommenderade node_redis-modulen. Lägg till den här raden i din package.json
fil, i avhängighetssektionen:
"node_redis": "0.11.x"
Om du vill kan du också inkludera hiredis
, ett högpresterande bibliotek skrivet i C, vilket node_redis
kommer att använda om det är tillgängligt:
"hiredis": "0.1.x"
Beroende på hur du skapade din Redis-databas och vilken PaaS-leverantör du använder kommer anslutningsinställningen att se lite annorlunda ut. Du behöver värd
, hamn
, Användarnamn
, och Lösenord
för din anslutning.
Heroku lagrar allt i config-variablerna som URL-adresser. Du måste extrahera den information du behöver från dem med Node url
modulen (config var för Redis To Go är process.env.REDISTOGO_URL
och för Redis Cloud process.env.REDISCLOUD_URL
). Den här koden går överst på din huvudapplikationsfil:
var redis = kräver ("redis"); var url = kräver ('url'); var redisURL = url.parse (YOUR_CONFIG_VAR_HERE); var client = redis.createClient (redisURL.host, redisURL.port); client.auth (redisURL.auth.split ( ':') [1]);
Om du skapade databasen för hand eller använd en annan leverantör än Heroku, bör du ha anslutningsalternativ och behörighetsuppgifter redan så använd bara dem:
var redis = kräver ("redis"); var klient = redis.createClient (YOUR_HOST, YOUR_PORT); client.auth (ditt_lösenord);
Därefter kan vi börja arbeta med kommunikation mellan instanser.
Det enklaste exemplet kommer bara att skicka information till andra instanser som du just har börjat. Du kan till exempel visa denna information i adminpanelen.
Innan vi gör någonting, skapa en annan ansluten namn client2
. Jag kommer att förklara varför vi behöver det senare.
Låt oss börja med att bara skicka meddelandet som vi började. Det är gjort med hjälp av publicera()
metod för kunden. Det tar två argument: Kanalen vi vill skicka meddelandet till och meddelandets text:
client.publish ("instanser", "start");
Det är allt du behöver för att skicka meddelandet. Vi kan lyssna på meddelanden i meddelande
händelsehanterare (observera att vi kallar detta på vår andra klient):
client2.on ("message", funktion (kanal, meddelande)
Återuppringningen passeras samma argument som vi överför till publicera()
metod. Låt oss nu visa denna information i konsolen:
om ((kanal == 'instanser') och (meddelande == 'start')) console.log ('Ny instans startad!'); );
Det sista att göra är att faktiskt prenumerera på den kanal vi ska använda:
client2.subscribe ( 'fall');
Vi använde två klienter för detta eftersom när du ringer prenumerera()
På klienten byts anslutningen till abonnent läge. Från den tidpunkten är de enda metoderna du kan ringa på Redis-servern PRENUMERERA
och SÄGA UPP
. Så om vi är i abonnent läge vi kan publicera()
meddelanden.
Om du vill kan du också skicka ett meddelande när instansen stängs av - du kan lyssna på SIGTERM
händelse och skicka meddelandet till samma kanal:
process.on ('SIGTERM', funktion () client.publish ('instances', 'stop'); process.exit (););
För att hantera det fallet i meddelande
handlare lägg till detta annars om
där inne:
annars om ((kanal == 'instanser') och (meddelande == 'stopp')) console.log ('Instance stopped!');
Så ser det ut så här efteråt:
client2.on ("message", funktion (kanal, meddelande) if ((channel == 'instances') och (message == 'start')) console.log ('Ny instans startad!'), annars om (kanal == 'instanser') och (meddelande == 'stopp')) console.log ('Instance stopped!'););
Observera att om du testar på Windows, stöder den inte SIGTERM
signal.
För att testa den lokalt, starta programmet några gånger och se vad som händer i konsolen. Om du vill testa uppsägningsmeddelandet ska du inte utfärda Ctrl + C
kommando i terminalen-istället, använd döda
kommando. Observera att detta inte stöds i Windows, så du kan inte kontrollera det.
Använd först ps
kommando för att kontrollera vilken id din process har-rör den till grep
för att underlätta:
$ ps -aux | grep your_apps_name
Den andra kolumnen i utgången är det ID som du tittar på. Tänk på att det också kommer att finnas en rad för det kommando du bara sprang. Utför nu döda
kommando med 15
för signalen-det är det SIGTERM
:
$ kill -15 PID
PID
är ditt process-ID.
Nu när du vet hur du använder Redis Pub / Sub-protokollet kan du gå utöver det enkla exemplet som presenterades tidigare. Här är några användarfall som kan vara till hjälp.
Det här är mycket användbart om du använder Express.js som ramverk. Om din ansökan stöder användarinloggningar eller nästan allt som använder sessioner, vill du se till att användarnas sessioner bevaras, oavsett om instansen startas om, flyttar användaren till en plats som hanteras av en annan eller användaren Växlas till en annan instans eftersom den ursprungliga gick ner.
Några saker att komma ihåg:
Vi behöver connect-redis-modulen. Versionen beror på vilken version av Express du använder. Den här är för Express 3.x:
"connect-redis": "1.4.7"
Och detta för Express 4.x:
"connect-redis": "2.x"
Skapa nu en annan Redis-anslutning som heter client_sessions
. Modulens användning beror igen på Express-versionen. För 3.x skapar du RedisStore
så här:
var RedisStore = kräver ('connect-redis') (express)
Och i 4.x måste du passera express-session
som parameter:
var session = kräver ("express-session"); var RedisStore = kräver ('connect-redis') (session);
Därefter är inställningen densamma i båda versionerna:
app.use (session (store: new RedisStore (client: client_sessions), hemlighet: "din hemliga sträng"));
Som du kan se passerar vi vår Redis-klient som klient
egenskapen till objektet som passerade till RedisStore
s konstruktör, och sedan passerar vi affären till session
konstruktör.
Nu om du startar din app, loggar du in eller startar en session och startar om instansen, kommer din session att bevaras. Detsamma händer när förekomsten byts till användaren.
Låt oss säga att du har en helt separerad instans (arbetare dyno på Heroku) för att göra mer resursätande arbete som komplicerade beräkningar, bearbeta data i databasen eller utbyta mycket data med en extern tjänst. Du kommer att vilja ha de "normala" fallen (och därmed användarna) att veta resultatet av det här arbetet när det är klart.
Beroende på om du vill att webinstanserna ska skicka data till arbetstagaren behöver du en eller två anslutningar (låt oss namnge dem client_sub
och client_pub
på arbetaren också). Du kan också återanvända alla anslutningar som inte prenumererar på något (som det du använder för Express-sessioner) istället för client_pub
.
Nu när användaren vill utföra åtgärden publicerar du meddelandet på den kanal som är reserverad bara för den här användaren och för det här specifika jobbet:
// detta går in i din förfrågan handler client_pub.publish ("JOB: USERID: JOBNAME: START", JSON.stringify (THEDATAYOUWANTTOSEND)); client_sub.subscribe ( 'JOBB: USERID: JOBB: PROGRESS');
Självklart måste du ersätta ANVÄNDAR ID
och JOBB NAMN
med lämpliga värden. Du borde också ha meddelande
handlare förberedd för client_sub
förbindelse:
klient_sub.on ("meddelande", funktion (kanal, meddelande) var USERID = channel.split (':') [1], om (meddelande == 'DONE') client_sub.unsubscribe (kanal); uttag [USERID] .emit (kanal, meddelande););
Detta extraherar ANVÄNDAR ID
från kanalnamnet (så se till att du inte prenumererar på kanaler som inte är relaterade till användarjobb på den här anslutningen) och skickar meddelandet till lämplig klient. Beroende på vilket WebSocket-bibliotek du använder kommer det att finnas något sätt att komma åt ett uttag med sitt ID.
Du kanske undrar hur arbetstagarens förekomst kan prenumerera på alla dessa kanaler. Självklart vill du inte bara göra några loopar på allt som möjligt ANVÄNDAR ID
s och JOBB NAMN
s. De psubscribe ()
Metoden accepterar ett mönster som argumentet, så det kan prenumerera på alla JOBB:*
kanaler:
// den här koden går till arbetstagarens instans // och du kallar det en gång client_sub.psubscribe ('JOB: *')
Det finns några problem du kan stöta på när du använder Pub / Sub:
meddelande
handler innan du ringer prenumerera()
, och det du ringer till prenumerera()
i ett fall innan du ringer publicera()
på den andra.