A cluster can take advantage of a computer’s multiple cores when running in a single thread (node.js main thread is a single thread)

Quick to use

const { isMaster, fork } = require("cluster");

if (isMaster) {
  const worker = fork();
  console.log('I am master');
} else {
  console.log('I am Worker');
}
Copy the code

IsMaster and isWorker are cluster attributes used to identify master and worker processes

It is important to note that some information cannot be stored globally in the way of the original scope, and other sharing mechanisms are needed

const { isMaster, fork } = require("cluster");
const store = {};

if (isMaster) {
  const worker = fork();
  store.name = 'test';
} else {
  console.log(store.name); // undefined
}
Copy the code

Commonly used API

setupMaster

cluster.setupMaster([settings])
Copy the code

Optimize code using setupMaster

const { isMaster, fork } = require("cluster");
const store = {};

if (isMaster) {
  const worker = fork();
  store.name = 'test';
} else {
  console.log(store.name); // undefined
}
Copy the code

The above code, master process and worker process code are clustered together, which is not readable. We can use setupMaster to optimize it

// master.js
const { fork, setupMaster } = require("cluster");

setupMaster({ exec: './worker.js' })
fork();
Copy the code
// worker.js
const { worker } = require('cluster');
console.log(worker.id);
Copy the code

In this way, the code of master process and worker process are separated, which is more readable

Fork () and fork events

cluster.fork([env]);  // fork worker process
Copy the code

After the fork succeeds, the fork event of the cluster is triggered. You can listen as follows

cluster.on('fork'.worker= >{... });Copy the code

Disconnect () and Disconnect events

cluster.disconnect([callback]); // The master process is available, disconnect all worker processes

worker.disconnect(); // Both the worker process and the main process are available. Disconnect a single worker process
Copy the code

When disconnect succeeds, it will trigger the Disconnect event of worker and Cluster

cluster.on('disconnect'.() = >{... }); worker.on('disconnect'.() = >{... });Copy the code

Kill () and exit events

worker.kill([signal='SIGTERM'])
Copy the code

After kill, the exit event of the cluster and worker is triggered

cluster.on('exit'.() = >{... }); worker.on('exit'.() = >{... });Copy the code

Send () and message events

// The master sends a message to the worker
worker.send(message[, sendHandle][, callback]);
// worker.process.send(message[, sendHandle[, options]][, callback]);

// The master receives the information transmitted by the worker
worker.on('message'.() = >{... });Copy the code
// The worker sends a message to the master
worker.send(message[, sendHandle][, callback]);
// worker.process.send(message[, sendHandle[, options]][, callback]);

// The worker receives the message sent by the master
worker.on('message'.() = >{... });Copy the code

Deep understanding of

cluster

A cluster is an instance of EventEmitter in both the master process and the worker process

// lib/internal/cluster/master.js.const cluster = new EventEmitter();

// lib/internal/cluster/child.js.const cluster = new EventEmitter();
Copy the code

However, it will return different objects depending on whether the master or child process is involved, which also helps that require is loaded at run time

// lib/cluster.js
// Use NODE_UNIQUE_ID to determine whether it is the master or the worker to introduce different modules
const childOrMaster = 'NODE_UNIQUE_ID' in process.env ? 'child' : 'master';
module.exports = require(`internal/cluster/${childOrMaster}`);
Copy the code

So, your code appears to be const cluster = required(‘cluster’), but the introduction is different

fork

Fork is the core API of cluster. Fork is implemented by child_process. Fork is divided into the following steps

  • SetupMaster: Sets the fork parameter
  • CreateWorkerProcess: Creates a child process
  • The Worker instance adds some events: such as the disconnect event above
  • Add workers array: at this point, we can get all worker processes through cluster.workers

setupMaster

Fork = child_process.fork = child_process.fork = child_process.fork = child_process.fork = child_process.fork = child_process

cluster.setupMaster = function (options) {
  // Override the default Settings with options
  const settings = {
    args: process.argv.slice(2),
    exec: process.argv[1].execArgv: process.execArgv,
    silent: false. cluster.settings, ... options }; . cluster.settings = settings;// Store Settings assignments.// The scheduling policy is initialized only once
  if (initialized === true) return process.nextTick(setupSettingsNT, settings);
  initialized = true; . schedulingPolicy = cluster.schedulingPolicy;Initialized can only set the scheduling policy once. The detailed scheduling policy will be discussed below. process.nextTick(setupSettingsNT, settings); . };Copy the code

SetupMaster can be called multiple times, and each call may modify cluster.setting. However, it only takes effect on subsequent forks and has no effect on previous forked worker processes

createWorkerProcess

CreateWorkerProcess is the core code for fork, where the child process is created

function createWorkerProcess(id, env) {
  // NODE_UNIQUE_ID is assigned here to determine whether it is a worker process based on the environment variable
  constworkerEnv = {... process.env, ... env,NODE_UNIQUE_ID: `${id}`}; // Set the child_process.fork environment variable
  const execArgv = cluster.settings.execArgv.slice(); // Set child_process.fork execArgv

  / /... Other parameter Settings...


  // createWorkerProcess (cluster.fork) creates a child process by child_process.fork
  return fork(cluster.settings.exec, cluster.settings.args, {
    cwd: cluster.settings.cwd,
    env: workerEnv,
    silent: cluster.settings.silent,
    windowsHide: cluster.settings.windowsHide,
    execArgv: execArgv,
    stdio: cluster.settings.stdio,
    gid: cluster.settings.gid, // Keep parent and child processes as a group
    uid: cluster.settings.uid // Set the child process user ID
  });
}
Copy the code

Worker Add event

worker.on('message',...). ; worker.process.once('exit',...). ; worker.process.once('disconnect',...). ; worker.process.on('internalMessage',...). ; process.nextTick(emitForkNT, worker);// Trigger the fork event mentioned above
Copy the code

The full fork version is shown below

cluster.fork = function (env) {
  cluster.setupMaster(); // set fork
  const id = ++ids; // Set the unique ID, NODE_UNIQUE_ID, of createWorkerProcess, with the increment ID
  const workerProcess = createWorkerProcess(id, env); // Create a child process


  // instantiate Worker
  const worker = new Worker({
    id: id,
    process: workerProcess
  });

  / /... Too much code for events, omitted

  process.nextTick(emitForkNT, worker); // Trigger the fork event
  cluster.workers[worker.id] = worker; // Store worker to workers
  return worker;
};
Copy the code

Worker

The worker returned by fork is not an instance of a child process

Take a look at the Worker object first

// lib/internal/cluster/worker.js
function Worker(options) {
  if(! (this instanceof Worker)) return new Worker(options);
  EventEmitter.call(this); .if (options.process) {
    this.process = options.process; // Wrap the child process.this.process.on('message'.(message, handle) = >
      this.emit('message', message, handle) ); }}// Workers inherit EventEmitter objects
Object.setPrototypeOf(Worker.prototype, EventEmitter.prototype);
Object.setPrototypeOf(Worker, EventEmitter);


Worker. send is process.send
Worker.prototype.send = function () {
  return this.process.send.apply(this.process, arguments);
};
Copy the code

Worker inherits EventEmitter and wraps process objects, adding events, attributes, and methods. For example, send methods and message events, which explains how workers implement IPC entirely with the help of process objects

Cluster. The worker and cluster. Workers

// In the parent process
cluster.workers // Retrieve all workers, pre-stored at fork

// In the child process
cluster.worker // Get the current worker object
Copy the code

Cluster. how does a worker get the worker object of the current worker process?

// lib/internal/cluster/child.js
cluster._setupWorker = function() {
  const worker = new Worker({
    id: +process.env.NODE_UNIQUE_ID | 0.// NODE_UNIQUE_ID Specifies the environment variable
    process: process,
    state: 'online'
  });

  cluster.worker = worker; // The worker instance can be obtained from cluster.worker, but this is the new Woker instance
};
Copy the code

Distribution strategy

Official:

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

The environment variables NODE_CLUSTER_SCHED_POLICY and cluster.schedulingPolicy can be modified directly

cluster.schedulingPolicy = 1 // 1 none, 2 rr(round-robin)

export NODE_CLUSTER_SCHED_POLICY = none / / none, rr
Copy the code