As we all know, JavaScript is a single-threaded language, and a single Node.js instance runs in a single thread. To make the most of a multi-core system, you sometimes need to enable a group of Node.js processes to handle load tasks. The Cluster module can create child processes that share server ports.

General multithreading work there is a main process and several sub-processes, the main process is responsible for deriving (creating) sub-process, the main process is as simple as possible, to prevent crashes. The child process handles the logic, that is, the work.

Multi-process for the server, safe, high performance (better utilization of CPU)

  1. Normal programs cannot create processes. Only system processes can create processes

  2. Processes are split, and only the main process can be split.

  3. The two separate processes execute the same code

  4. Parent and child processes can share handles, and a port is a handle

const cluster = require('cluster');
/ / split ends,
if (cluster.isMaster) { // If the main process is split
    cluster.fork();
};
Copy the code

Run as many processes as you can on as many cpus, not the more the better

const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isMaster) {
    for (const i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }
    console.log('I'm the main process');
    cluster.on('exit'.(worker, code, signal) = > {
        console.log('Working process${worker.process.pid}Have withdrawn from `);
    });
} else {
    console.log('I'm a child process')}Copy the code

Main process = daemon process

Child process = worker process

Normally child processes do the work, and the master process does the administration.

const cluster = require('cluster');
const os = require('os');
const process = require('process');

if (cluster.isMaster) {
    for (const i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }
    console.log('I'm the main process');
} else {
    console.log(process.pid);
    http.createServer((req, res) = > {
        res.write('aaaa');
        res.end();
    }).listen(8080);
    console.log('Port number 8080');
}

Copy the code
  • The above program does not report an error because child processes can share port numbers.

  • Process. pid: indicates the PID of a process.

  • Process scheduling, multiple processes, will only start the second process when the first process is full, and then start the third process when the second process is full,

The overhead and scheduling of processes are very performance – consuming. Computer operations are fast and slow when seen by the naked eye.

Multiple processes do not cause a deadlock, which means that while reading or writing to a file, other programs can restrict access to the file.

Worker processes are created by the child_process.fork() method, so they can use IPC to communicate with the parent process, allowing the processes to alternate connection services.

The Cluster module supports two methods for distributing connections.

The first approach (and the default on all platforms except Windows) is a loop approach, in which the main process listens for ports, receives new connections and circulates them to the worker process, using built-in tricks to prevent the worker process from overloading.

In the second method, the main process creates the listening socket and sends it to the interested worker process, which is responsible for receiving the connection directly.

In theory, the second method should be the most efficient. In practice, however, distribution becomes unstable due to the unpredictable scheduling mechanism of the operating system. It is possible that two out of eight processes share 70% of the load.

Because server.listen() hands most of the work off to the main process, there are three ways that a normal Node.js process can differ from a cluster worker process:

Server.listen ({fd: 7}) Because messages are passed to the main process, file descriptor 7 in the parent process is listened on and passes handles to the worker process instead of listening to the worker process pointed to by file descriptor 7. Server.listen (handle) explicitly listens for a handle, causing the worker process to use the handle directly instead of communicating with the main process. Server.listen (0) Normally, this call causes the server to listen on random ports. But in cluster mode, all worker processes receive the same “random” port each time they call LISTEN (0). In essence, such ports are only randomly assigned for the first time and become predictable thereafter. If you want to use a separate port, you should generate a port number based on the worker process ID.

Node.js does not support routing logic. Therefore, when designing applications, you should not rely too much on in-memory data objects, such as sessions and logins.

Because worker processes are independent processes, they can be shut down or regenerated at any time as needed without affecting the normal operation of other processes. As long as there are alive worker processes, the server can continue to process connections. If there are no surviving worker processes, existing connections are lost and new connections are rejected. Node.js does not automatically manage the number of worker processes. Instead, it is up to specific applications to manage process pools based on actual needs.

Although the Cluster module is primarily used in network-related situations, it can also be used in other situations that require worker processes.

Worker

The Worker object contains all the common information and methods about the Worker process. In the main process, cluster.workers can be used to get it. In the worker process, you can use cluster.worker to get it.

  • Disconnect events

Similar to cluster.on(‘disconnect’) event, but specific to this worker process.

cluster.fork().on('disconnect'.() = > {
  // The worker process is disconnected.
});
Copy the code
  • Error event

This event is the same as that provided by child_process.fork().

In a worker process, process.on(‘error’) can also be used.

  • The exit events

Similar to cluster.on(‘exit’) event, but specific to this worker process.

const worker = cluster.fork();
worker.on('exit'.(code, signal) = > {
  if (signal) {
    console.log('The working process has been signaled${signal}Kill `);
  } else if(code ! = =0) {
    console.log('Working process exits, exit code:${code}`);
  } else {
    console.log('Working process exits successfully'); }});Copy the code
  • Listening event

Similar to the cluster.on(‘listening’) event, but specific to the worker process, this event is not fired in the worker process.

cluster.fork().on('listening'.(address) = > {
  // The worker process is listening.
});
Copy the code
  • Message event

Similar to the cluster.on(‘message’) event, but specific to the worker process.

You can also use process.on(‘message’) within a worker process.

Here is an example of using a messaging system. It keeps a count of the number of HTTP requests received by the worker process in the main process:

const cluster = require('cluster');
const http = require('http');

if (cluster.isMaster) {

  // Trace HTTP requests.
  let numReqs = 0;
  setInterval(() = > {
    console.log('Number of requests =${numReqs}`);
  }, 1000);

  // Count requests.
  function messageHandler(msg) {
    if (msg.cmd && msg.cmd === 'notifyRequest') {
      numReqs += 1; }}// Start the worker and listen for messages containing notifyRequest.
  const numCPUs = require('os').cpus().length;
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  for (const id in cluster.workers) {
    cluster.workers[id].on('message', messageHandler); }}else {

  // The worker process has an HTTP server.
  http.Server((req, res) = > {
    res.writeHead(200);
    res.end('Hello world \n');

    // Notify the main process that the request was received.
    process.send({ cmd: 'notifyRequest' });
  }).listen(8000);
}
Copy the code
  • The online event

Similar to the cluster.on(‘online’) event, but specific to this worker process, this event does not fire in the worker process.

cluster.fork().on('online'.() = > {
  // The working process is online.
});
Copy the code
  • worker.disconnect()

Within a worker process, calling this method closes all servers, waits for the ‘close’ event of those servers to execute, and then closes the IPC pipe.

Inside the main process, an internal message is sent to the worker process, causing the worker process itself to call.disconnect().

ExitedAfterDisconnect is set.

When a server is closed, it will not receive new connections, but new connections will be received by other worker processes that are listening. Existing connections can be closed. When all connections are closed, the IPC pipe to the worker process is closed, allowing the worker process to die gracefully, as shown in server.close().

The above case applies only to server connections. The worker process does not automatically close the client connection. The Disconnect method does not wait for the client connection to close before exiting.

In the worker process, there is process.disconnect, too, but it’s not this function; it’s disconnect().

Because a long-running server connection may prevent the worker process from disconnecting, you can send a message and have the application take the appropriate action to close the connection. You can also set a timeout to shut down the worker process if the ‘disconnect’ event is not triggered after a certain amount of time.

if (cluster.isMaster) {
  const worker = cluster.fork();
  let timeout;

  worker.on('listening'.(address) = > {
    worker.send('shutdown');
    worker.disconnect();
    timeout = setTimeout(() = > {
      worker.kill();
    }, 2000);
  });

  worker.on('disconnect'.() = > {
    clearTimeout(timeout);
  });

} else if (cluster.isWorker) {
  const net = require('net');
  const server = net.createServer((socket) = > {
    // The connection never ends.
  });

  server.listen(8000);

  process.on('message'.(msg) = > {
    if (msg === 'shutdown') {
      // Gracefully close all connections to the server.}}); }Copy the code
  • worker.id

Each new derived worker process is assigned its own unique number, which is stored in the ID.

This number can be used as an index in cluster.workers while the worker process is still alive.

Disconnect events

Triggered when the IPC pipe of the worker process is disconnected. Possible causes of event firing include a worker process exiting gracefully, being killed, or manually disconnecting (such as calling worker.disconnect()).

There may be a delay between the ‘disconnect’ and ‘exit’ events. These events can be used to detect if a process is stuck during cleanup or if there is a long-running connection.

cluster.on('disconnect'.(worker) = > {
  console.log('Work process #${worker.id}Disconnected ');
});
Copy the code

The exit events

The Cluster module will trigger an ‘exit’ event when any worker process is shut down.

This can be used to restart the worker process (by calling.fork() again).

cluster.on('exit'.(worker, code, signal) = > {
  console.log('Worker process %d closed (%s). Restarting... ',
              worker.process.pid, signal || code);
  cluster.fork();
});
Copy the code

The fork event

When a new worker process is spawned, the Cluster module will trigger a ‘fork’ event. Can be used to log worker process activity and generate a custom timeout.

const timeouts = [];
function errorMsg() {
  console.error('Connection error');
}

cluster.on('fork'.(worker) = > {
  timeouts[worker.id] = setTimeout(errorMsg, 2000);
});
cluster.on('listening'.(worker, address) = > {
  clearTimeout(timeouts[worker.id]);
});
cluster.on('exit'.(worker, code, signal) = > {
  clearTimeout(timeouts[worker.id]);
  errorMsg();
});
Copy the code

Listening event

When a worker process calls LISTEN (), the server on the worker process fires a ‘listening’ event, and the cluster on the main process fires a ‘listening’ event.

The event handle is executed with two arguments, where worker contains the worker process object and address contains the following connection properties: address, port, and addressType. These parameters are useful when a worker process listens on multiple addresses simultaneously.

cluster.on('listening'.(worker, address) = > {
  console.log(
    'The worker process is connected to${address.address}:${address.port}`);
});
Copy the code

AddressType Available values include:

4 (TCPv4)

6 (TCPv6)

1 domain (Unix sockets)

‘UDP4’ or ‘UDP6’ (UDP V4 or v6)

Message event

Triggered when the cluster main process receives a message from any worker process.

The online event

When a new worker process is spawned, the worker process should respond with an on-line message. This event is triggered when the main process receives a go online message. The difference between a ‘fork’ event and a ‘online’ event is that ‘fork’ is triggered when the main process spawns a worker process and ‘online’ is triggered when the worker process is running.

cluster.on('online'.(worker) = > {
  console.log('The worker process is spawned and responds');
});
Copy the code

The setup event

Emitted whenever.setupMaster() is called.

The Settings object is the cluster.settings object when.setupMaster() is called, and can only be queried because.setupMaster() can be called multiple times at a point in time.

If accuracy is important, use cluster.Settings.

cluster.isMaster

True if the process is the main process. This is determined by process.env.node_unique_id. If process.env.node_unique_id is not defined, isMaster is true

cluster.isWorker

True if the process is not the main process (as opposed to cluster.ismaster)

cluster.worker

A reference to the current worker process object. Does not apply to the main process.

cluster.workers

This is a hash table that stores active worker process objects, using ids as key names. This makes it easy to traverse all work processes. Can only be called in the main process.

Workers will be removed from cluster.workers after disconnecting and exiting. The sequence of the two events cannot be determined in advance. It is guaranteed, however, that the removal of Cluster.workers is completed before the last of the ‘disconnect’ and ‘exit’ events is triggered.

// Iterate over all work processes.
function eachWorker(callback) {
  for (const id in cluster.workers) {
    callback(cluster.workers[id]);
  }
}
eachWorker((worker) = > {
  worker.send('Notify all working processes');
});
Copy the code

Using the unique ID of the worker process is the easiest way to locate the worker process.

socket.on('data'.(id) = > {
  const worker = cluster.workers[id];
});
Copy the code

[Reference article -node website] (nodejs.org/dist/latest…)