How to scale Node.js applications with clustering
Introduction
Node.js is a server-side JavaScript runtime environment used to create scalable and high-speed web applications. Scalability is a critical factor for any web application that intends to handle large volumes of traffic. One of the most popular ways to scale Node.js applications is by using clustering.
Clustering is a technique that involves creating multiple Node.js processes that work together to handle the application load. This means that each request arriving at the application is distributed across different processes so that they can be processed simultaneously. It also helps ensure that the application is fault-tolerant and continues to run even if one of the processes fails.
In this manual, we will explain how to configure clustering and how to scale your Node.js application with it.
Step 1: Install Node.js
Before starting, make sure you have Node.js installed on your system. You can download it from the official Node.js website and install it on your system.
Step 2: Create a Node.js application file
Create a Node.js application file that listens for client requests. This file will be the entry point for your application.
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello World!');
});
server.listen(3000);
Step 3: Configure said application in cluster mode
This is an example of a basic Node.js application that runs in cluster mode:
// app.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master process ID: ${process.pid}`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// Handle worker exit and restart
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker process ID ${worker.process.pid} died`);
console.log('Forking a new worker...');
cluster.fork();
});
} else {
// Worker processes will enter this block
// Create a simple HTTP server
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello, world!
');
}).listen(3000);
console.log(`Worker process ID ${process.pid} started`);
}
In this example, the master process is responsible for forking worker processes, while the worker processes create an HTTP server and handle incoming requests. Each worker process listens on the same port (3000 in this case), which allows them to share the incoming traffic.
We are going to review each part of the code and explain its purpose:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
- The cluster module allows us to create and manage a cluster of worker processes.
- The http module provides functionality to create an HTTP server.
- The os module is used to determine the number of CPU cores available on the system using the cpus().length property.
if (cluster.isMaster) {
console.log(`Master process ID: ${process.pid}`);
// Create worker processes
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// Handle the exit and restart of worker processes
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker process with ID ${worker.process.pid} has exited`);
console.log('Creating a new worker process...');
cluster.fork();
});
} else {
// Worker processes will enter this block
// Create a basic HTTP server
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello, world!
');
}).listen(3000);
console.log(`Worker process with ID ${process.pid} has started`);
}
- The if (cluster.isMaster) block runs only in the master process. It checks if the current process is the master process using the isMaster property of the cluster module.
- If it is the master process, it prints the master process ID to the console using process.pid and then proceeds to create several worker processes.
- The for loop creates the desired worker processes (in this case, numCPUs, which represents the number of CPU cores).
- The cluster.on('exit', ...) block is responsible for handling the exit of worker processes. Each time a worker process ends (exits), the worker process ID is logged to the console and a new worker process is created to replace it.
- If the current process is not the master process (i.e., it is a worker process), it enters the else block.
- Within the else block, each worker process creates an HTTP server using http.createServer() and listens on port 3000.
- The server responds with a 'Hello, world!' message for each request received.
- The worker process ID is displayed in the console using process.pid to indicate that the worker process has started.
With this configuration, the master process is in charge of managing the worker processes. It creates them based on the number of available CPU cores and restarts any worker process that exits. The worker processes handle incoming HTTP requests and respond to them. By distributing the load among the worker processes, the application can handle more concurrent requests and improve performance.
Feel free to modify the code according to your requirements and application logic.
Step 4: Run the application
-
Save the previous code in a file named
app.js. -
Open the terminal and navigate to the directory containing
app.js. -
Start the application using the
nodecommand:$ node app.js
Step 5: Test the application
Test the application by opening a web browser and typing the server's IP address and the port on which the application is running.
http://localhost:3000
Conclusion
Clustering is a popular technique for scaling Node.js applications. This manual has shown how to configure and use clustering to distribute the application load across multiple child processes. By following these steps, you will be able to scale your Node.js application and make it more fault-tolerant.