Node.js is een server-side JavaScript runtime die gebruik maakt van een event-driven, non-blocking input-output (I/O) model. Het wordt algemeen gebruikt voor het bouwen van snelle en schaalbare webapps. Ook kan je profiteren van de grote gemeenschap en een rijke bibliotheek aan modules die verschillende taken en processen vereenvoudigen.

Clustering verbetert de prestaties van Node.js applicaties door ze op meerdere processen te laten draaien. Door deze techniek kunnen ze het volledige potentieel van een multi-core systeem benutten.

In dit artikel wordt uitgebreid ingegaan op clustering in Node.js en hoe het de prestaties van een applicatie beïnvloedt.

Wat is clustering?

Standaard draaien Node.js applicaties op een enkele thread. Dit single-threaded karakter betekent dat Node.js niet alle cores kan gebruiken in een multi-core systeem – wat de meeste systemen op dit moment zijn.

Node.js kan nog steeds meerdere verzoeken tegelijk verwerken door gebruik te maken van niet-blokkerende I/O-bewerkingen en asynchrone programmeertechnieken.

Zware rekentaken kunnen echter de event loop blokkeren en ervoor zorgen dat de applicatie niet meer reageert. Daarom wordt Node.js geleverd met een native clustermodule – ongeacht het single-threaded karakter – om te profiteren van de totale verwerkingskracht van een multi-core systeem.

Het uitvoeren van meerdere processen maakt gebruik van de verwerkingskracht van meerdere cores van de central processing unit (CPU) om parallelle verwerking mogelijk te maken, responstijden te verkorten en doorvoer te verhogen. Dit verbetert op zijn beurt de prestaties en schaalbaarheid van Node.js applicaties.

Hoe werkt clusteren?

Met de Node.js clustermodule kan een Node.js applicatie een cluster van gelijktijdig draaiende childprocessen maken, die elk een deel van de werklast van de applicatie verwerken.

Bij het initialiseren van de clustermodule maakt de applicatie het primaire proces aan, dat vervolgens de childprocessen in workerprocessen forkt. Het primaire proces fungeert als load balancer en verdeelt de werklast over de workerprocessen terwijl elk workerproces luistert naar binnenkomende verzoeken.

De Node.js clustermodule heeft twee methoden om inkomende verbindingen te verdelen.

  • De round-robin benadering – Het primaire proces luistert op een poort, accepteert nieuwe verbindingen en verdeelt de workloadgelijkmatig om ervoor te zorgen dat geen enkel proces overbelast raakt. Dit is de standaard aanpak op alle besturingssystemen behalve Windows.
  • De tweede benadering – Het primaire proces maakt de luistersocket aan en stuurt deze naar “geïnteresseerde” werkers, die binnenkomende verbindingen direct accepteren.

Theoretisch zou de tweede benadering – die gecompliceerder is – betere prestaties moeten leveren. Maar in de praktijk is de verdeling van de verbindingen erg onevenwichtig. De Node.js documentatie vermeldt dat 70% van alle verbindingen terecht komt bij slechts twee van de acht processen.

Je Node.js applicaties clusteren

Laten we nu eens kijken naar de effecten van clustering in een Node.js applicatie. Deze tutorial gebruikt een Express applicatie die opzettelijk een zware computationele taak uitvoert om de event loop te blokkeren.

Draai deze applicatie eerst zonder clustering. Registreer vervolgens de prestaties met een benchmarking tool. Vervolgens wordt clustering geïmplementeerd in de applicatie en wordt de benchmarking herhaald. Vergelijk tot slot de resultaten om te zien hoe clustering de prestaties van je applicatie verbetert.

Aan de slag

Om deze tutorial te begrijpen moet je bekend zijn met Node.js en Express. Om je Express server in te stellen:

  1. Begin met het aanmaken van het project.
    mkdir cluster-tutorial
  2. Navigeer naar de applicatiemap en maak twee bestanden, no-cluster.js en cluster.js, door het onderstaande commando uit te voeren:
    cd cluster-tutorial && touch no-cluster.js && touch cluster.js
  3. Initialiseer NPM in je project:
    npm init -y
  4. Installeer tot slot Express door het onderstaande commando uit te voeren:
    npm install express

Een niet-geclusterde applicatiemaken

Voeg in je no-cluster.js bestand het onderstaande codeblok toe:

const express = require("express");
const PORT = 3000;

const app = express();

app.get("/", (req, res) => {
  res.send("Response from server");
});

app.get("/slow", (req, res) => {
  //Start timer 
  console.time("slow");

  // Generate a large array of random numbers
  let arr = [];
  for (let i = 0; i < 100000; i++) {
  arr.push(Math.random());
  }

  // Perform a heavy computation on the array
  let sum = 0;
  for (let i = 0; i  {
  console.log(`Server listening on port ${PORT}`);
});

Bovenstaand codeblok maakt een express server die draait op poort 3000. De server heeft twee routes, een root (/) route en een /slow route. De root route stuurt een respons naar de client met het bericht: “Response from server.”

De /slow route voert echter opzettelijk zware berekeningen uit om de event loop te blokkeren. Deze route start een timer en vult dan een array met 100.000 willekeurige getallen met behulp van een for loop.

Vervolgens wordt met behulp van een andere for loop elk getal in de gegenereerde array gekwadrateerd en opgeteld. De timer stopt wanneer dit is voltooid en de server antwoordt met de resultaten.

Start je server door het onderstaande commando uit te voeren:

node no-cluster.js

Doe vervolgens een GET verzoek naar localhost:3000/slow.

Als je gedurende deze tijd andere verzoeken probeert te doen aan je server – zoals aan de root route (/) – zijn de responses traag omdat de /slow route de event loop blokkeert.

Een geclusterde applicatie maken

Maak childprocessen aan met behulp van de clustermodule om te voorkomen dat je applicatie niet meer reageert en opeenvolgende verzoeken blokkeert tijdens zware rekentaken.

Elk childproces draait zijn event loop en deelt de serverpoort met het parentproces, waardoor de beschikbare resources beter worden gebruikt.

Importeer eerst de Node.js cluster en os module in je cluster.js bestand. De clustermodule maakt het mogelijk om childprocessen aan te maken om de werklast te verdelen over meerdere CPU cores.

De module os geeft informatie over het besturingssysteem van je computer. Je hebt deze module nodig om het aantal beschikbare cores op je systeem op te vragen en ervoor te zorgen dat je niet meer childprocessen aanmaakt dan cores op je systeem.

Voeg het onderstaande codeblok toe om deze modules te importeren en het aantal cores op je systeem op te vragen:

const cluster = require("node:cluster");
const numCores = require("node:os").cpus().length;

Voeg vervolgens het onderstaande codeblok toe aan je cluster.js bestand:

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);
  console.log(`This machine has ${numCores} cores`);

  // Fork workers.
  for (let i = 0; i  {
  console.log(`worker ${worker.process.pid} died`);

  // Replace the dead worker
  console.log("Starting a new worker");
  cluster.fork();
  });
}

Het codeblok hierboven controleert of het huidige proces het primaire of worker proces is. Als dat zo is, spawnt het codeblok childprocessen gebaseerd op het aantal cores in je systeem. Vervolgens wordt er geluisterd naar de exit gebeurtenis op de processen en worden ze vervangen door nieuwe processen.

Tot slot verpak je alle gerelateerde express logica in een else blok. Je voltooide cluster.js bestand zou moeten lijken op het onderstaande codeblok.

//cluster.js
const express = require("express");
const PORT = 3000;
const cluster = require("node:cluster");
const numCores = require("node:os").cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);
  console.log(`This machine has ${numCores} cores`);

  // Fork workers.
  for (let i = 0; i  {
  console.log(`worker ${worker.process.pid} died`);

  // Replace the dead worker
  console.log("Starting a new worker");
  cluster.fork();
  });
} else {
  const app = express();

  app.get("/", (req, res) => {
    res.send("Response from server");
  });

  app.get("/slow", (req, res) => {
   console.time("slow");
  // Generate a large array of random numbers
  let arr = [];
  for (let i = 0; i < 100000; i++) {
  arr.push(Math.random());
    }

   // Perform a heavy computation on the array
   let sum = 0;
  for (let i = 0; i  {
  console.log(`Server listening on port ${PORT}`);
  });
}

Na het implementeren van clustering zullen meerdere processen verzoeken afhandelen. Dit betekent dat je applicatie responsief blijft, zelfs tijdens een zware rekentaak.

De prestaties benchmarken met eenloadtest

Om de effecten van clustering in een Node.js applicatie nauwkeurig te demonstreren en weer te geven, kun je het npm pakket loadtest gebruiken om de prestaties van je applicatie voor en na clustering te vergelijken.

Voer het onderstaande commando uit om loadtest globaal te installeren:

npm install -g loadtest

Het pakket loadtest voert een loadtest uit op een opgegeven HTTP/WebSockets URL.

Start vervolgens je no-cluster.js bestand op in een terminal. Open dan een andere terminal en voer de onderstaande belastingstest uit:

loadtest http://localhost:3000/slow -n 100 -c 10

De bovenstaande opdracht stuurt 100 verzoeken met een concurrency van 10 naar je niet-geclusterde app. Het uitvoeren van dit commando levert de onderstaande resultaten op:

Resultaten belastingstest niet-geclusterde app.
Resultaten belastingstest niet-geclusterde app.

Gebaseerd op de resultaten duurde het ongeveer 100 seconden om alle verzoeken zonder clustering te voltooien, en het duurde 12 seconden om het meest uitgebreide verzoek te voltooien.

De resultaten kunnen variëren afhankelijk van je systeem.

Stop vervolgens met het uitvoeren van het no-cluster.js bestand en start je cluster.js bestand op in een terminal. Open vervolgens een andere terminalinstantie en voer deze belastingstest uit:

loadtest http://localhost:3000/slow -n 100 -c 10

Het bovenstaande commando stuurt 100 verzoeken met een concurrency 10 naar je geclusterde app.

Het uitvoeren van dit commando levert de onderstaande resultaten op:

Resultaat belastingstest geclusterde app.
Resultaat belastingstest geclusterde app.

Met het clusteren duurde het 0,13 seconden (136 ms) om de verzoeken te voltooien, een enorme afname ten opzichte van de 100 seconden die de niet-geclusterde app nodig had. Bovendien duurde het 41 ms om het meest uitgebreide verzoek van de geclusterde app te voltooien.

Deze resultaten tonen aan dat het implementeren van clustering de prestaties van je applicatie aanzienlijk verbetert. Merk op dat je procesbeheersoftware zoals PM2 moet gebruiken om je clustering in productieomgevingen te beheren.

Node.js gebruiken met de Applicatie Hosting van Kinsta

Kinsta is een hostingbedrijf dat het eenvoudig maakt om je Node.js applicaties te deployen. Het hostingplatform is gebouwd op het Google Cloud Platform, dat een betrouwbare infrastructuur biedt die is ontworpen om veel verkeer aan te kunnen en complexe applicaties te ondersteunen. Dit verbetert de prestaties van Node.js applicaties.

Kinsta biedt verschillende features voor Node.js deployments, zoals interne databaseverbindingen, Cloudflare integratie, GitHub deployments en Google C2 Machines.

Deze features maken het eenvoudig om Node.js applicaties te gebruiken en te beheren en stroomlijnen het ontwikkelproces.

Om je Node.js applicatie te deployen op Kinsta’s Applicatie Hosting, is het cruciaal om de code en bestanden van je applicatie naar de door jou gekozen Git provider (Bitbucket, GitHub of GitLab) te pushen.

Zodra je repository is ingesteld, volg je deze stappen om je Express applicatie te deployen naar Kinsta:

  1. Log in of maak een account aan om je MyKinsta dashboard te bekijken.
  2. Autoriseer Kinsta met je Git provider.
  3. Klik op Applicaties op de linker zijbalk, klik dan op Applicatie toevoegen.
  4. Selecteer de repository en de branch waarvan je wilt deployen.
  5. Geef je app een unieke naam en kies een locatie voor het datacenter.
  6. Configureer vervolgens je bouwomgeving. Selecteer de Standaard buildmachine configuratie met de aanbevolen Nixpacks optie voor deze demo.
  7. Gebruik alle standaardconfiguraties en klik dan op Applicatie maken.

Samenvatting

Clustering in Node.js maakt het mogelijk om meerdere workerprocessen aan te maken om de werklast te verdelen, waardoor de prestaties en schaalbaarheid van Node.js applicaties verbeteren. Het goed implementeren van clustering is cruciaal om het volledige potentieel van deze techniek te bereiken.

Het ontwerpen van de architectuur, het beheren van de resource-allocatie en het minimaliseren van de netwerklatency zijn vitale factoren bij het implementeren van clustering in Node.js. Het belang en de complexiteit van deze implementatie zijn de redenen waarom procesmanagers zoals PM2 gebruikt zouden moeten worden in productieomgevingen.

Wat is jouw mening over Node.js clustering? Heb je het eerder gebruikt? Deel het in de commentsectie!

Jeremy Holcombe Kinsta

Content & Marketing Editor bij Kinsta, WordPress Web Developer en Content Writer. Buiten alles wat met WordPress te maken heeft, geniet ik van het strand, golf en films. En verder heb ik last van alle problemen waar andere lange mensen ook tegenaan lopen ;).