Hoe gaat Node.js in het algemeen om met 10.000 gelijktijdige verzoeken?

Ik begrijp dat Node.js een enkele thread en een gebeurtenislus gebruikt om verzoeken slechts één voor één te verwerken (wat niet-blokkerend is). Maar toch, hoe werkt dat, laten we zeggen 10.000 gelijktijdige verzoeken. De gebeurtenislus verwerkt alle verzoeken? Zou dat niet te lang duren?

Ik begrijp (nog) niet hoe het sneller kan dan een multi-threaded webserver. Ik begrijp dat multi-threaded webserver duurder zal zijn in middelen (geheugen, CPU), maar zou het niet nog steeds sneller zijn? Ik heb het waarschijnlijk mis; leg alsjeblieft uit hoe deze single-thread sneller is bij veel verzoeken, en wat het doorgaans doet (op hoog niveau) bij het verwerken van veel verzoeken, zoals 10.000.

En ook, zal die single-thread goed schalen met die grote hoeveelheid? Houd er rekening mee dat ik net Node.js begin te leren.


Antwoord 1, autoriteit 100%

Als u deze vraag moet stellen, bent u waarschijnlijk niet bekend met wat de meeste webapplicaties/services doen. Je denkt waarschijnlijk dat alle software dit doet:

user do an action
       │
       v
 application start processing action
   └──> loop ...
          └──> busy processing
 end loop
   └──> send result to user

Dit is echter niet hoe webapplicaties, of welke applicatie dan ook met een database als back-end, werken. Web-apps doen dit:

user do an action
       │
       v
 application start processing action
   └──> make database request
          └──> do nothing until request completes
 request complete
   └──> send result to user

In dit scenario besteedt de software het grootste deel van de looptijd met 0% CPU-tijd, wachtend tot de database terugkeert.

Multithreaded netwerk-app:

Multithreaded netwerk-apps verwerken de bovenstaande werklast als volgt:

request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request

Dus de thread besteedt het grootste deel van zijn tijd aan het gebruik van 0% CPU, wachtend tot de database gegevens retourneert. Terwijl ze dit deden, moesten ze het geheugen toewijzen dat nodig is voor een thread die een volledig afzonderlijke programmastapel voor elke thread bevat, enz. Ze zouden ook een thread moeten starten die niet zo duur is als het starten van een volledig proces goedkoop.

Enkeldraadse gebeurtenislus

Omdat we het grootste deel van onze tijd besteden aan het gebruik van 0% CPU, waarom zouden we dan geen code uitvoeren als we geen CPU gebruiken? Op die manier krijgt elk verzoek nog steeds dezelfde hoeveelheid CPU-tijd als toepassingen met meerdere threads, maar we hoeven geen thread te starten. Dus we doen dit:

request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response

In de praktijk retourneren beide benaderingen gegevens met ongeveer dezelfde latentie, aangezien het de responstijd van de database is die de verwerking domineert.

Het belangrijkste voordeel hier is dat we geen nieuwe thread hoeven te maken, dus we hoeven niet heel veel malloc te doen, wat ons zou vertragen.

Magisch, onzichtbaar inrijgen

Het schijnbaar mysterieuze is hoe beide benaderingen hierboven erin slagen om de werklast “parallel” te laten lopen? Het antwoord is dat de database is gethreaded. Onze single-threaded app maakt dus feitelijk gebruik van het multi-threaded gedrag van een ander proces: de database.

Waar singlethreaded-benadering faalt

Een singlethreaded app faalt enorm als je veel CPU-berekeningen moet doen voordat de gegevens worden geretourneerd. Nu bedoel ik niet een for-lus die het databaseresultaat verwerkt. Dat is nog grotendeels O(n). Wat ik bedoel zijn dingen als Fourier-transformatie (mp3-codering bijvoorbeeld), ray tracing (3D-rendering) enz.

Een andere valkuil van singlethreaded apps is dat het slechts één CPU-kern gebruikt. Dus als je een quad-core server hebt (niet ongebruikelijk tegenwoordig), gebruik je de andere 3 cores niet.

Waar multithreaded aanpak faalt

Een app met meerdere threads faalt enorm als je veel RAM per thread moet toewijzen. Ten eerste betekent het RAM-gebruik zelf dat u niet zoveel verzoeken kunt verwerken als een app met één thread. Erger nog, malloc is traag. Door heel veel objecten toe te wijzen (wat gebruikelijk is voor moderne webframeworks) kunnen we mogelijk langzamer zijn dan apps met één thread. Dit is waar node.js meestal wint.

Een use-case die multithreaded uiteindelijk erger maakt, is wanneer u een andere scripttaal in uw thread moet uitvoeren. Eerst moet je meestal de hele runtime voor die taal malloceren, daarna moet je de variabelen die door je script worden gebruikt, malloceren.

Dus als je netwerk-apps schrijft in C of go of java, dan valt de overhead van threading meestal mee. Als je een C-webserver schrijft om PHP of Ruby te bedienen, dan is het heel eenvoudig om een ​​snellere server in javascript of Ruby of Python te schrijven.

Hybride aanpak

Sommige webservers gebruiken een hybride aanpak. Nginx en Apache2 implementeren bijvoorbeeld hun netwerkverwerkingscode als een threadpool van gebeurtenislussen. Elke thread voert een gebeurtenislus uit die gelijktijdig verzoeken met één thread verwerkt, maar verzoeken worden verdeeld over meerdere threads.

Sommige single-threaded architecturen gebruiken ook een hybride benadering. In plaats van meerdere threads vanuit één proces te starten, kunt u meerdere toepassingen starten, bijvoorbeeld 4 node.js-servers op een quad-coremachine. Vervolgens gebruik je een load balancer om de werklast over de processen te verdelen.

In feite zijn de twee benaderingen technisch identieke spiegelbeelden van elkaar.


Antwoord 2, autoriteit 7%

Wat u lijkt te denken, is dat het grootste deel van de verwerking wordt afgehandeld in de knooppuntgebeurtenislus. Node leidt het I/O-werk eigenlijk af naar threads. I/O-bewerkingen duren doorgaans orden van grootte langer dan CPU-bewerkingen, dus waarom moet de CPU daarop wachten? Bovendien kan het besturingssysteem I/O-taken al heel goed aan. Omdat Node niet wacht, bereikt het zelfs een veel hoger CPU-gebruik.

Zie bij wijze van analogie NodeJS als een ober die de bestellingen van de klant opneemt terwijl de I/O-koks ze in de keuken bereiden. Andere systemen hebben meerdere koks, die de bestelling van een klant opnemen, de maaltijd bereiden, de tafel afruimen en pas daarna de volgende klant bedienen.


Antwoord 3, autoriteit 4%

Verwerkingsstappen voor model met enkele thread voor gebeurtenislus:

  • Klanten Verzend verzoek naar webserver.

  • Node JS Web Server onderhoudt intern een beperkte threadpool om
    diensten verlenen aan de verzoeken van de klant.

  • Node JS Web Server ontvangt die verzoeken en plaatst ze in een
    Wachtrij. Het staat bekend als “Evenementenwachtrij”.

  • Node JS-webserver heeft intern een component, bekend als “Event Loop”.
    Waarom het deze naam heeft gekregen, is dat het een onbepaalde lus gebruikt om te ontvangen
    verzoeken en verwerken.

  • Event Loop gebruikt alleen Single Thread. Het is het belangrijkste hart van Node JS
    Platformverwerkingsmodel.

  • Event Loop controleert of een Client Request in de Event Queue wordt geplaatst. Als
    wacht dan niet voor onbepaalde tijd op binnenkomende verzoeken.

  • Zo ja, neem dan één klantverzoek op uit de evenementwachtrij

    1. Start proces dat verzoek van klant
    2. Als dat clientverzoek geen blokkerende IO vereist
      Bewerkingen, vervolgens alles verwerken, reactie voorbereiden en verzenden
      terug naar klant.
    3. Als dat clientverzoek een aantal blokkerende IO-bewerkingen vereist, zoals:
      interactie met database, bestandssysteem, externe services, dan is het
      zal een andere benadering volgen
  • Controleert de beschikbaarheid van threads uit de interne threadpool
  • Pak één thread op en wijs dit clientverzoek aan die thread toe.
  • Die thread is verantwoordelijk voor het aannemen, verwerken,
    voer Blocking IO-bewerkingen uit, bereid antwoord voor en stuur het terug
    naar de Event Loop

    heel mooi uitgelegd door @Rambabu Posa voor meer uitleg, gooi deze Link


Antwoord 4, autoriteit 2%

Ik begrijp dat Node.js een enkele thread en een gebeurtenislus gebruikt om
verwerk verzoeken slechts één voor één (wat niet blokkeert).

Het kan zijn dat ik verkeerd begrijp wat je hier hebt gezegd, maar “een voor een” klinkt alsof je de op gebeurtenissen gebaseerde architectuur misschien niet helemaal begrijpt.

In een “conventionele” (niet-gebeurtenisgestuurde) applicatiearchitectuur, besteedt het proces veel tijd aan wachten tot er iets gebeurt. In een op gebeurtenissen gebaseerde architectuur zoals Node.js wacht het proces niet alleen, het kan doorgaan met ander werk.

Bijvoorbeeld: u krijgt een verbinding van een client, u accepteert deze, u leest de verzoekheaders (in het geval van http), en u begint op het verzoek te reageren. Misschien lees je de hoofdtekst van het verzoek, over het algemeen stuur je wat gegevens terug naar de klant (dit is een bewuste vereenvoudiging van de procedure, alleen maar om het punt aan te tonen).

In elk van deze fasen wordt de meeste tijd besteed aan het wachten op gegevens die van de andere kant komen – de werkelijke tijd die wordt besteed aan het verwerken in de hoofd-JS-thread is meestal vrij minimaal.

Als de status van een I/O-object (zoals een netwerkverbinding) zodanig verandert dat het moet worden verwerkt (bijv. gegevens worden ontvangen op een socket, een socket wordt beschrijfbaar, enz.), wordt de hoofd Node.js JS-thread gewekt met een lijst met items die moeten worden verwerkt.

Het vindt de relevante gegevensstructuur en zendt een gebeurtenis uit op die structuur die ervoor zorgt dat callbacks worden uitgevoerd, die de binnenkomende gegevens verwerken, of meer gegevens naar een socket schrijven, enz. Zodra alle I/O-objecten die nodig zijn verwerking zijn verwerkt, wacht de hoofdthread van Node.js JS opnieuw totdat wordt verteld dat er meer gegevens beschikbaar zijn (of een andere bewerking is voltooid of een time-out is opgetreden).

De volgende keer dat het wordt gewekt, kan dit te wijten zijn aan een ander I/O-object dat moet worden verwerkt, bijvoorbeeld een andere netwerkverbinding. Elke keer worden de relevante callbacks uitgevoerd en daarna gaat het weer in slaapstand, wachtend tot er iets anders gebeurt.

Het belangrijkste punt is dat de verwerking van verschillende verzoeken afwisselend is en niet het ene verzoek van begin tot eind verwerkt en vervolgens naar het volgende gaat.

Naar mijn mening is het belangrijkste voordeel hiervan dat een traag verzoek (je probeert bijvoorbeeld 1 MB aan responsgegevens naar een mobiel telefoonapparaat te verzenden via een 2G-gegevensverbinding, of je doet een erg trage databasequery ) blokkeert geen snellere.

In een conventionele multi-threaded webserver heb je meestal een thread voor elk verzoek dat wordt afgehandeld, en het zal ALLEEN dat verzoek verwerken totdat het is voltooid. Wat gebeurt er als je veel langzame verzoeken hebt? Je eindigt met veel van je threads die rondhangen met het verwerken van deze verzoeken, en andere verzoeken (dit kunnen heel eenvoudige verzoeken zijn die heel snel kunnen worden afgehandeld) komen erachter in de wachtrij.

Er zijn tal van andere op gebeurtenissen gebaseerde systemen behalve Node.js, en deze hebben meestal vergelijkbare voor- en nadelen in vergelijking met het conventionele model.

Ik zou niet beweren dat op gebeurtenissen gebaseerde systemen sneller zijn in elke situatie of met elke werkbelasting – ze werken meestal goed voor I/O-gebonden werkbelastingen, niet zo goed voor CPU-gebonden.


Antwoord 5, autoriteit 2%

Toevoegen aan slebetman antwoord:
Als u zegt dat Node.JS10.000 gelijktijdige verzoeken aankan, zijn het in wezen niet-blokkerende verzoeken, d.w.z. deze verzoeken hebben voornamelijk betrekking op databasequery’s.

Intern verwerkt event loopvan Node.JSeen thread pool, waarbij elke thread een non-blocking requesten event loop blijven luisteren naar meer verzoeken na het delegeren van werk naar een van de threads van de thread pool. Wanneer een van de threads het werk voltooit, stuurt het een signaal naar de event loopdat het klaar is, oftewel callback. event loopverwerk vervolgens deze callback en stuur het antwoord terug.

Aangezien je nieuw bent bij NodeJS, lees meer over nextTickom te begrijpen hoe de gebeurtenislus intern werkt.
Lees blogs op http://javascriptissexy.com, ze waren erg nuttig voor mij toen ik begon met JavaScript/NodeJS.


Antwoord 6

Toevoegen aan slebetman‘s antwoord voor meer duidelijkheid over wat er gebeurt tijdens het uitvoeren van de code.

De interne threadpool in nodeJs heeft standaard slechts 4 threads. en het is niet alsof het hele verzoek is gekoppeld aan een nieuwe thread uit de threadpool, de hele uitvoering van het verzoek gebeurt net als elk normaal verzoek (zonder enige blokkeringstaak), alleen dat wanneer een verzoek lang duurt of een zware operatie heeft zoals db call, een bestandsbewerking of een http-verzoek, wordt de taak in de wachtrij geplaatst voor de interne threadpool die wordt geleverd door libuv. En aangezien nodeJs standaard 4 threads in de interne threadpool biedt, wacht elke 5e of volgende gelijktijdige aanvraag totdat een thread vrij is en zodra deze bewerkingen voorbij zijn, wordt de callback naar de callback-wachtrij gepusht. en wordt opgepikt door de gebeurtenislus en stuurt het antwoord terug.

Hier komt nog een andere informatie dat het niet één enkele terugbelwachtrij is, er zijn veel wachtrijen.

  1. VolgendeTick-wachtrij
  2. Micro-taakwachtrij
  3. Timerswachtrij
  4. IO-callback-wachtrij (verzoeken, bestandsops, db ops)
  5. IO Poll-wachtrij
  6. Controleer Fasewachtrij of SetImmediate
  7. Behandelaarswachtrij sluiten

Telkens wanneer er een verzoek binnenkomt, wordt de code uitgevoerd in deze volgorde van terugbellen in de wachtrij.

Het is niet zo dat wanneer er een blokkeringsverzoek is, het aan een nieuwe thread wordt gekoppeld. Er zijn standaard maar 4 threads. Dus daar staat weer een wachtrij.

Telkens wanneer in een code een blokkeringsproces zoals het lezen van bestanden plaatsvindt, wordt een functie aangeroepen die thread uit de threadpool gebruikt en als de bewerking eenmaal is voltooid, wordt de callback doorgegeven aan de respectieve wachtrij en vervolgens in de volgorde uitgevoerd.

p>

Alles wordt in de wachtrij geplaatst op basis van het type terugbelverzoek en verwerkt in de hierboven vermelde volgorde.


Antwoord 7

Het blokkerende deelvan het multithreaded-blokkeringssysteem maakt het minder efficiënt. De geblokkeerde thread kan nergens anders voor worden gebruikt, terwijl deze wacht op een reactie.

Terwijl een niet-blokkerend single-threaded systeem optimaal gebruik maakt van zijn single-thread systeem.

Zie onderstaand schema:
voer hier de afbeeldingsbeschrijving in
voer hier de afbeeldingsbeschrijving in


Antwoord 8

Hier is een goede uitleg van dit middelgrote artikel:

Gegeven een NodeJS-toepassing, aangezien Node single-threaded is, bijvoorbeeld als de verwerking een Promise.all omvat die 8 seconden duurt, betekent dit dan dat het clientverzoek dat na dit verzoek komt, acht seconden moet wachten?
Nee. NodeJS-gebeurtenislus is single-threaded. De hele serverarchitectuur voor NodeJS is niet single-threaded.

Alvorens in te gaan op de Node-serverarchitectuur, om te kijken naar het typische multithreaded request-responsmodel, zou de webserver meerdere threads hebben en wanneer gelijktijdige verzoeken bij de webserver komen, kiest de webserver threadOne uit de threadPool en threadOne verwerkt requestOne en reageert op clientOne en wanneer het tweede verzoek binnenkomt, pikt de webserver de tweede thread op uit de threadPool en pakt requestTwo op en verwerkt het en reageert op clientTwo. threadOne is verantwoordelijk voor alle soorten bewerkingen die requestOne heeft geëist, inclusief het blokkeren van IO-bewerkingen.

Het feit dat de thread moet wachten op het blokkeren van IO-bewerkingen, maakt hem inefficiënt. Met dit soort model kan de webserver slechts zoveel verzoeken verwerken als er threads in de threadpool zijn.

NodeJS Web Server onderhoudt een beperkte threadpool om diensten te verlenen aan klantverzoeken. Meerdere clients doen meerdere verzoeken aan de NodeJS-server. NodeJS ontvangt deze verzoeken en plaatst ze in de EventQueue.
NodeJS-server heeft een interne component die EventLoop wordt genoemd, een oneindige lus die verzoeken ontvangt en verwerkt. Deze EventLoop is single threaded. Met andere woorden, EventLoop is de luisteraar voor de EventQueue.
We hebben dus een gebeurteniswachtrij waar de verzoeken worden geplaatst en we hebben een gebeurtenislus die naar deze verzoeken luistert in de gebeurteniswachtrij. Wat gebeurt er nu?
De listener (de gebeurtenislus) verwerkt het verzoek en als het in staat is om het verzoek te verwerken zonder IO-blokkeringsbewerkingen nodig te hebben, dan zou de gebeurtenislus het verzoek zelf verwerken en het antwoord zelf naar de client terugsturen.
Als de huidige aanvraag gebruikmaakt van blokkerende IO-bewerkingen, ziet de gebeurtenislus of er threads beschikbaar zijn in de threadpool, pakt één thread op uit de threadpool en wijst het specifieke verzoek toe aan de gekozen thread. Die thread voert de blokkerende IO-bewerkingen uit en stuurt het antwoord terug naar de gebeurtenislus en zodra het antwoord in de gebeurtenislus komt, stuurt de gebeurtenislus het antwoord terug naar de client.

Hoe is NodeJS beter dan het traditionele multithreaded request-responsmodel?
Met het traditionele multithreaded request/response-model krijgt elke client een andere thread, waarbij, net als bij NodeJS, de eenvoudigere verzoeken allemaal rechtstreeks door de EventLoop worden afgehandeld. Dit is een optimalisatie van threadpoolbronnen en er zijn geen overheadkosten voor het maken van de threads voor elk klantverzoek.

Other episodes