Preface:

Basic Concepts To understand An application program contains at least one process, and a process contains at least one thread.

  • Process is a running activity of a program on a data set in a computer. It is the basic unit of resource allocation and scheduling in the system
  • A Thread is the smallest unit in which an operating system can schedule operations. It is contained within the process and is the actual operating unit within the process.

Node features:

  • The main thread is a single thread;
  • There is only one main thread in a process.
  • Event-driven, asynchronous, non-blocking I/O;
  • Can be used in high concurrency scenarios.

Nodejs does not have multiple threads, so in order to take full advantage of multi-core cpus, sub-processes can be used to implement load balancing of the kernel.

Node needs to solve the following problems:

  • Node blocks while doing time-consuming calculations.
  • How do NODE start child processes
  • How to implement process daemons during development

There are too many concepts, so let’s start with a case study to see what problems single threading can cause.

Disadvantages of single threading

// file: question.js
const http = require('http');
http.createServer((req, res) = > {
  if (req.url === '/sum') { / / sum
    var endTime = new Date().getTime() + 10000
    while (new Date().getTime() < endTime) {}
    res.end('sum')}else {
    res.end('end');
  }
}).listen(3000);
Copy the code

steps

  • node question.js
  • Open your browser and access /sum on a tab1. Quickly open another tab2 and access /.

What will happen? We find that tab1 is going around in circles, tab2 is also going around in circles, which is a very strange phenomenon. It is understandable that tab1 is spinning around because it takes us 10 seconds, but tab2 also takes 10 seconds before it can be accessed. That’s weird.

The problem is that if someone else blocks the browser for 10 seconds, you block it for 10 seconds. That’s a hard question to accept. Therefore, it is concluded that Node is not suitable for CPU intensive services.

How to solve this problem?

To solve this problem, we introduce child processes.

file: calc.js

var endTime = new Date().getTime() + 10000
while (new Date().getTime() < endTime) {}

process.send({
    time: new Date().getTime()+' '
});
Copy the code

Transformation question. Js

file: question.js
const http = require('http');
const {fork} = require('child_process');
const path = require('path');
http.createServer((req, res) = > {
  if (req.url === '/sum') { / / sum
      // var endTime = new Date().getTime() + 10000
      // while (new Date().getTime() < endTime) {}
      // res.end('sum')
      let childProcess = fork('calc.js', {
        cwd: path.resolve(__dirname)
      });
      childProcess.on('message'.function (data) {
        res.end(data.time + ' '); })}else {
    res.end('end');
  }
}).listen(3001);
Copy the code

Restart Node question.js and find tab2, which will not block.

Summary: Node as a server needs to start child processes to handle CPU-intensive operations. To prevent the main thread from being blocked

Use of child processes (child_process)

Methods used

  • Spawn Generates the child process asynchronously
  • Fork generates a new Node.js process and invokes the specified module using the established IPC communication channel, which allows messages to be sent between parent and child levels.
  • Exec produces a shell and runs commands in that shell
  • ExecFile does not need to generate a shell

spawn

Spawn, which allows you to create a child process

let { spawn } = require("child_process");
let path = require("path");
// Execute the sub_process.js file using the node command
let childProcess = spawn("node"['sub_process.js'] and {cwd: path.resolve(__dirname, "test"), // Find the file in the test directory
  stdio: [0.1.2]});// Monitor error
childProcess.on("error".function(err) {
  console.log(err);
});
// Listen for close events
childProcess.on("close".function() {
  console.log("close");
});
// Listen for the exit event
childProcess.on("exit".function() {
  console.log("exit");
});
Copy the code

Stdio is a very interesting property, we’ve given 0,1,2 so what does that mean? stdio

  1. 0 respectively corresponding to the current process of main process. The stdin, process. Stdout, process. Stderr, means the main process and the child to share the standard input and output
{CWD: path.resolve(__dirname, "test");} // Start childProcess = spawn("node",['sub_process.js'], {CWD: path.resolve(__dirname, "test"); [0, 1, 2]});Copy the code

You can print the sub_process.js execution result under the current process

  1. If the stdio parameter is not provided by default, the default value is stdio:[‘pipe’], which means that the communication between processes can only be achieved through streams
let { spawn } = require("child_process"); let path = require("path"); Let childProcess = spawn("node",['sub_process.js'], {CWD: spawn("node",['sub_process.js']) Path. The resolve (__dirname, "test"), the stdio: [' pipe '] / /}) by means of flow; Childprocess.stdout. on('data',function(data){console.log(data); }); // The child writes process.stdout.write('hello') as standard output;Copy the code
  1. To communicate in IPC mode, set the value to stdio:[‘pipe’,’pipe’,’pipe’,’ipc’]. You can communicate with on(‘message’) and send methods
let { spawn } = require("child_process"); let path = require("path"); Let childProcess = spawn("node",['sub_process.js'], {CWD: spawn("node",['sub_process.js']) Path. The resolve (__dirname, "test"), the stdio: [' pipe, pipe, pipe, 'the ipc'] / /}) by means of flow; Childprocess. on('message',function(data){console.log(data); }); // Send a message process.send('hello');Copy the code
  1. You can also pass ignore to inherit to represent the standard input and output of the default shared parent process

Generate a separate process

let { spawn } = require("child_process");
let path = require("path");
// Execute the sub_process.js file using the node command
let child = spawn('node'['sub_process.js'] and {cwd:path.resolve(__dirname,'test'),
    stdio: 'ignore'.detached:true // Separate threads
});
child.unref(); // Give up control
Copy the code

Effect: After a thread is started, it relinquishes control of the thread. So we don’t have to use the controls too much in the background.

fork

By default, the new process can communicate via IPC

let { fork } = require("child_process");
let path = require("path");
// Execute the sub_process.js file using the node command
let childProcess = fork('sub_process.js', {
  cwd: path.resolve(__dirname, "test")}); childProcess.on('message'.function(data){
    console.log(data);
});
Copy the code

Fork is spawn based, and you can pass in a silent attribute to set whether input and output are shared

Principle of the fork

function fork(filename,options){
    let stdio = ['inherit'.'inherit'.'inherit']
    if(options.silent){ // Ignore the input and output of the child process if it is quiet
        stdio = ['ignore'.'ignore'.'ignore']
    }
    stdio.push('ipc'); // Ipc is supported by default
    options.stdio = stdio
    return spawn('node',[filename],options)
}
Copy the code

execFile

Execute a file directly using the node command

let childProcess = execFile("node"['./test/sub_process'].function(err,stdout,stdin){
    console.log(stdout); 
});
Copy the code

Internally, the spawn method is called

exec

let childProcess = exec("node './test/sub_process'".function(err,stdout,stdin){
    console.log(stdout)
});
Copy the code

The internal call is execFile, and the above three methods are all based on spawn

Implement the cluster

// file cluster.js main thread
// The internal principle is multi-process
// Distributed front-end and back-end clusters share multiple functions to share the work
// Cluster can achieve load balancing of multiple cpus in the general case
// Different processes listen on the same port number
const {fork}  = require('child_process');
const cpus = require('os').cpus().length;
const path = require('path');

// Start a service in the main process
const http = require('http');
let server = http.createServer(function (req,res) {
    res.end(process.pid+' '+ ' main end')
}).listen(3000);

for(let i = 0 ; i < cpus-1 ; i++ ){
    let cp = fork('server.js', {cwd:path.resolve(__dirname,'worker'),stdio: [0.1.2.'ipc']});
    cp.send('server',server); // I can pass in an HTTP or TCP service as the second parameter in IPC mode
}
// Multiple requests are I/O intensive
/ / cluster cluster
Copy the code
// file worker/server.js child process
const http = require('http');

process.on('message'.function (data,server) {
    http.createServer(function (req,res) {
        
        res.end(process.pid+' '+ 'end')
    }).listen(server); // Multiple processes monitor the same port number
})

Copy the code
// file http.get.js request script
const http = require('http');


for(let i =0 ; i < 10000; i++){ http.get({port:3000.hostname:'localhost'
    },function (res) {
        res.on('data'.function (data) {
            console.log(data.toString())
        })
    })
}
Copy the code

After the request script is started, you can clearly find that the process PID of the request is not the same PID.

The cluster module implements the cluster

let cluster = require("cluster");
let http = require("http");
let cpus = require("os").cpus().length;
const workers = {};
if (cluster.isMaster) {
    cluster.on('exit'.function(worker){
        console.log(worker.process.pid,'death')
        let w = cluster.fork();
        workers[w.pid] = w;
    })
  for (let i = 0; i < cpus; i++) {
    letworker = cluster.fork(); workers[worker.pid] = worker; }}else {
  http
    .createServer((req, res) = > {
      res.end(process.pid+' '.'pid');
    })
    .listen(3000);
  console.log("server start",process.pid);
}
Copy the code

The appellate code is a bit anti-human, but this process exists in c++ as well.

Another way

// file  

const cluster = require('cluster');
const cpus = require('os').cpus();

// Import file

cluster.setupMaster({
    exec: require('path').resolve(__dirname,'worker/cluster.js')}); cluster.on('exit'.function (worker) {
    console.log(worker.process.pid);
    cluster.fork(); // Start a process
})
for(let i = 0; i < cpus.length ; i++){ cluster.fork();// child_process fork creates the child process with the current file
    // And isMaster is false else method is executed
}
// PM2 is restarted in cluster mode
/ / module
Copy the code
// node worker/cluster.js 
// Our project has a lot of logic
  const http = require('http');
  http.createServer((req, res) = > {

    if (Math.random() > 0.5) {
      SDSADADSSA();
    }
    // The same port number can be listened on in the cluster environment
    res.end(process.pid + ':' + 'end')
  }).listen(3000);
Copy the code

Pm2 application

Pm2 allows you to deploy your application to all cpus on the server, enabling multi-process management, monitoring, and load balancing

Install the pm2

NPM install pm2 -g # install pm2 pm2 start server.js --watch -i Max # Start pm2 list pm2 kill # Kill all processes pm2 start NPM -- run dev # Start the NPM scriptCopy the code

Pm2 configuration file

pm2 ecosystem
Copy the code

Configure automatic project deployment

module.exports = {
  apps : [{
    name: 'my-project'.script: 'server.js'.// Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/
    args: 'one two'.instances: 2.autorestart: true.watch: false.max_memory_restart: '1G'.env: {
      NODE_ENV: 'development'
    },
    env_production: {
      NODE_ENV: 'production'}}].deploy : {
    production : {
      user : 'root'.host : '39.106.14.146'.ref  : 'origin/master'.repo : 'https://github.com/wakeupmypig/pm2-deploy.git'.path : '/home'.'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production'}}};Copy the code
Git clone pm2 deploy file.config. js production setup git clone pm2 deploy file.configCopy the code