Kako skalirati svoj Node.js poslužitelj pomoću klasteriranja

Skalabilnost je vruća tema u tehnologiji, a svaki programski jezik ili okvir pruža svoj način rukovanja velikim opterećenjima prometa.

Danas ćemo vidjeti jednostavan i izravan primjer klasterizacije Node.js. Ovo je tehnika programiranja koja će vam pomoći paralelizirati kod i ubrzati izvedbu.

“Jedna instanca Node.js radi u jednoj niti. Da bi iskoristio prednosti višejezgrenih sustava, korisnik će ponekad htjeti pokrenuti klaster Node.js procesa kako bi podnio opterećenje. "

- Node.js dokumentacija

Izradit ćemo jednostavan web poslužitelj koristeći Koa, koji je stvarno sličan Expressu u smislu upotrebe.

Cjelovit primjer dostupan je u ovom spremištu Github.

Što ćemo izgraditi

Izgradit ćemo jednostavan web poslužitelj koji će djelovati kako slijedi:

  1. Naš će poslužitelj primiti POSTzahtjev, pretvarat ćemo se da nam korisnik šalje sliku.
  2. Kopirat ćemo sliku iz datotečnog sustava u privremeni direktorij.
  3. Okomito ćemo ga okrenuti pomoću Jimpa, biblioteke za obradu slika za Node.js.
  4. Spremit ćemo je u datotečni sustav.
  5. Izbrisat ćemo ga i korisniku ćemo poslati odgovor.

Naravno, ovo nije stvarna svjetska aplikacija, ali je prilično blizu jedne. Samo želimo izmjeriti blagodati korištenja klastera.

Postavljanje projekta

Koristit ću yarnza instaliranje ovisnosti i inicijalizaciju projekta:

Budući da je Node.js jednonitni, ako se naš web poslužitelj sruši, ostat će neaktivan sve dok ga neki drugi postupak ponovno ne pokrene. Dakle, instalirat ćemo zauvijek, jednostavan demon koji će ponovno pokrenuti naš web poslužitelj ako se ikada sruši.

Također ćemo instalirati Jimp, Koa i Koa Router.

Početak rada s Koom

Ovo je struktura mape koju trebamo stvoriti:

Imat ćemo srcmapu koja sadrži dvije JavaScript datoteke: cluster.jsi standard.js.

Prva će biti datoteka u kojoj ćemo eksperimentirati s clustermodulom. Drugi je jednostavan Koa poslužitelj koji će raditi bez ikakvog grupiranja.

U moduledirektoriju ćemo stvoriti dvije datoteke: job.jsi log.js.

job.jsizvršit će posao manipulacije slikom. log.jszapisat će svaki događaj koji se dogodi tijekom tog postupka.

Modul Dnevnik

Modul dnevnika bit će jednostavna funkcija koja uzima argument i zapisuje ga u stdout(slično console.log).

Također će dodati trenutnu vremensku oznaku na početku dnevnika. To će nam omogućiti da provjerimo kada je postupak započeo i izmjerimo njegovu izvedbu.

Modul Job

Bit ću iskren, ovo nije lijepa i super optimizirana skripta. To je samo jednostavan posao koji će nam omogućiti da naglasimo svoj stroj.

Web poslužitelj Koa

Stvorit ćemo vrlo jednostavan web poslužitelj. Odgovorit će na dvije rute s dvije različite HTTP metode.

Moći ćemo izvršiti GET zahtjev dana //localhost:3000/. Koa će odgovoriti jednostavnim tekstom koji će nam pokazati trenutni PID (ID procesa).

Druga ruta prihvatit će samo POST zahtjeve na /flipstazi i izvršit će posao koji smo upravo stvorili.

Također ćemo stvoriti jednostavan međuprodukt koji će postaviti X-Response-Timezaglavlje. To će nam omogućiti da izmjerimo izvedbu.

Sjajno! Sada možemo započeti s upisivanjem poslužitelja node ./src/standard.jsi testirati naše rute.

Problem

Upotrijebimo moj stroj kao poslužitelj:

  • Macbook Pro 15-inčni 2016
  • 2,7 GHz Intel Core i7
  • 16 GB RAM-a

Ako podnesem POST zahtjev, gornja skripta poslat će mi odgovor za ~ 3800 milisekundi. Nije tako loše, s obzirom na to da slika na kojoj trenutno radim iznosi oko 6,7 MB.

Mogu pokušati poslati više zahtjeva, ali vrijeme odziva neće se previše smanjiti. To je zato što će se zahtjevi izvršavati sekvencijalno.

Pa, što bi se dogodilo kad bih pokušao podnijeti 10, 100, 1000 istodobnih zahtjeva?

Napravio sam jednostavnu Elixir skriptu koja izvodi više istodobnih HTTP zahtjeva:

Odabrao sam Elixir jer je zaista lako stvoriti paralelne procese, ali možete koristiti što god želite!

Testiranje deset istodobnih zahtjeva - bez grupiranja

Kao što vidite, iz našeg iexa (Elixir REPL) izričemo 10 istodobnih procesa.

Poslužitelj Node.js odmah će kopirati našu sliku i početi je okretati.

Prvi će se odgovor zabilježiti nakon 16 sekundi, a posljednji nakon 40 sekundi.

Tako dramatično smanjenje performansi! Sa samo 10 istodobnih zahtjeva,smanjili smo performanse web poslužitelja za 950%!

Predstavljamo klasteriranje

Sjećate se što sam spomenuo na početku članka?

Da bi iskoristio prednosti višejezgrenih sustava, korisnik će ponekad htjeti pokrenuti klaster Node.js procesa za rukovanje opterećenjem.

Ovisno o poslužitelju na kojem ćemo pokretati našu Koa aplikaciju, mogli bismo imati različit broj jezgri.

Svaka će jezgra biti odgovorna za pojedinačno rukovanje teretom. U osnovi će svaki HTTP zahtjev biti zadovoljen jednom jezgrom.

Tako na primjer - moj stroj, koji ima osam jezgri, obrađivat će osam istodobnih zahtjeva.

Sada možemo računati koliko CPU-a imamo zahvaljujući osmodulu:

cpus()Metoda će vratiti niz predmeta koje opisuju naše procesora. Njegovu duljinu možemo vezati za konstantu koja će se zvati numWorkers, jer to je broj radnika koje ćemo koristiti.

Sada smo spremni zahtijevati clustermodul.

Sada nam je potreban način da podijelimo naš glavni proces u Nrazličite procese.

Nazvat ćemo naš glavni proces masteri ostale procese workers.

Node.js clustermodul nudi metodu koja se naziva isMaster. Vratit će logičku vrijednost koja će nam reći je li trenutni postupak usmjeren od strane radnika ili majstora:

Sjajno. Zlatno pravilo ovdje je da ne želimo služiti našu aplikaciju Koa u okviru glavnog postupka.

Želimo stvoriti Koa aplikaciju za svakog radnika, pa će se, kad stigne zahtjev, prvi besplatni radnik pobrinuti za to.

cluster.fork()Metoda će stati naš cilj:

Ok, u početku to može biti malo nezgodno.

Kao što vidite u gornjoj skripti, ako je našu skriptu izvršio glavni postupak, proglasit ćemo konstantu pozvanu workers. To će stvoriti radnika za svaku jezgru našeg CPU-a i pohranit će sve podatke o njima.

Ako se ne osjećate usvojeno u sintaksi, upotreba […Array(x)].map()je ista kao:

Jednostavno više volim koristiti nepromjenjive vrijednosti dok razvijam aplikaciju s visokom istodobnošću.

Dodavanje Koa

Kao što smo već rekli, ne želimo služiti našu aplikaciju Koa u okviru glavnog postupka.

Kopirajmo strukturu naše aplikacije Koa u elseizjavu, pa ćemo biti sigurni da će je posluživati ​​radnik:

Kao što vidite, u isMasterizjavu smo dodali i nekoliko slušatelja događaja :

Prva će nam reći da je iznjedren novi radnik. Drugi će stvoriti novog radnika kad se padne još jedan radnik.

Na taj će način glavni postupak biti odgovoran samo za stvaranje novih radnika i njihovo organiziranje. Svaki će radnik služiti primjerak Koe koji će biti dostupan u :3000luci.

Testiranje deset istodobnih zahtjeva - uz grupiranje

Kao što vidite, prvi smo odgovor dobili nakon otprilike 10 sekundi, a posljednji nakon otprilike 14 sekundi. To je nevjerojatan napredak u odnosu na prethodnih 40 sekundi vremena odziva!

Uputili smo deset istodobnih zahtjeva, a poslužitelj Koa odmah ih je uzeo osam. Kada je prvi radnik poslao svoj odgovor klijentu, uzeo je jedan od preostalih zahtjeva i obradio ga!

Zaključak

Node.js ima nevjerojatnu sposobnost rukovanja velikim opterećenjima, ali ne bi bilo pametno zaustaviti zahtjev dok poslužitelj ne završi svoj postupak.

U stvari, web-poslužitelji Node.js mogu obraditi tisuće istodobnih zahtjeva samo ako klijentu odmah pošaljete odgovor.

Najbolja praksa bila bi dodati pub / sub sučelje za razmjenu poruka koristeći Redis ili bilo koji drugi nevjerojatan alat. Kada klijent pošalje zahtjev, poslužitelj započinje komunikaciju u stvarnom vremenu s drugim uslugama. Za to su potrebni skupi poslovi.

Balanseri tereta također bi puno pomogli u razdvajanju velikih prometnih opterećenja.

Još jednom, tehnologija nam pruža beskrajne mogućnosti, a mi ćemo sigurno pronaći pravo rješenje za proširenje naše aplikacije do beskonačnosti i šire!