First of all, we should know that node is executed by single thread, but today's computer has developed to multi-core CPU. Therefore, it has become an important problem to solve the problem of node using multi-core CPU server.
I Multi process architecture
Analysis: in master JS folder, through child_process to copy sub processes, and os to access the number of CPU s to control how many sub processes are copied. At the same time, the monitoring of 3000 ~ 4000 random ports is enabled in the sub processes.
// master.js let fork = require('child_process').fork; let cpus = require('os').cpus(); for(let i = 0; i< cpus.length; i ++){ fork('./worker.js'); } // worker.js let http = require('http'); http.createServer((req, res) => { res.writeHead(200,{'Content-Type':'text/plain'}); res.end('hello word'); }).listen(Math.round((3 + Math.random()) * 1000 ) , '127.0.0.1');
1. Create sub process
spawn | Start the subprocess to execute the command |
exec | Start a child process to execute commands, but it has a callback function to know the status of the child process |
execFile | Start a sub process to execute the executable |
fork | Similar to spawn, the difference is that it only needs to specify the js file module to be executed to create the Node sub process |
Please check the official documents for details: child_process creation
2. Inter process communication
Analysis: the parent-child process listens for incoming messages through on and send s messages. The communication between them will create an IPC channel. Only through this channel can the message transmission of the parent-child process be realized.
// master.js main process let cp = require('child_process'); let worker = cp.fork('./worker.js'); // Used to listen for information from child processes worker.on('message', ( data ) =>{ console.log('Received message from child process: ', data); }) // Used to send information to child processes worker.send('Hello, subprocess '); // worker.js work process // Send information to the main process in 3 seconds setTimeout(()=>{ process.send({name: 'Stone Mountain'}); },3000) // Used to listen for messages from the parent process process.on('message', (data) => { console.log('I got it.', data); })
- Principle of interprocess communication
The full name of IPC is inter process communication, that is, inter process communication. The purpose of inter process communication is to enable different processes to access resources and coordinate work with each other.
Before creating a child process, the parent process will first create an IPC channel and listen to it, and then really create a child process, and tell the child process the file descriptor of this IPC channel through the environment variable( File descriptor interpretation ). During the startup process, the child process connects the existing IPC channel according to the file descriptor, so as to complete the connection between the parent and child processes. Its behavior is similar to that of socket, but it omits the network layer and is completed directly in the system kernel, which is more efficient.
3. Handle transfer
First, we introduce a concept, handle. What is handle? A handle is a reference used to identify a resource. It contains a file descriptor pointing to an object. For example, handles can be used to identify a server socket object, a client socket object, a UDP socket, and a pipe.
Let's imagine such a scenario. For example, an interface is accessed by a large number of users at the same time. At this time, the node of a single thread is more difficult. Although we can make use of the characteristics of multiple processes, how to make multiple processes listen to the same port at the same time has become a difficult problem, which requires the so-called handle transfer function.
Analysis: the main process passes the} server to the sub process, starts listening and then closes the server, so that the sub process can directly listen to the port. Each request is randomly responded by a sub process, and the response between them is preemptive.
// parent.js start the main process let cp = require('child_process'); let cpus = require('os').cpus let child2 = cp.fork('child2.js'); let child1 = cp.fork('child1.js'); console.log(cpus) let server = require('net').createServer(); server.on('connection', socket => { socket.end('handled by parent'); }) server.listen(1337, () => { child1.send('server1', server); child2.send('server2', server); server.close(); }) // child1 process let http = require('http'); let server = http.createServer((req, res ) => { if(req.url === '/') { res.writeHead(200, {'Content-Type':'text/plain'}); res.end('handled by child1, pid is' + process.pid + '\n'); } }) process.on('message', ( m, tcp ) => { if( m === 'server1') { tcp.on('connection', socket => { server.emit('connection', socket); }) } }) // child2 process let http = require('http'); let server = http.createServer((req, res ) => { if(req.url === '/index'){ res.writeHead(200, {'Content-Type':'text/plain'}); res.end('handled by child2, pid is' + process.pid + '\n'); } }) process.on('message', ( m, tcp ) => { console.log(22222) if( m === 'server2') { tcp.on('connection', socket => { server.emit('connection', socket); }) } })
II The road of cluster stability
By making full use of the resources of multi-core CPU, we can meet a large number of client requests, but we still need to consider many problems, such as performance, multiple process survival status, process restart and so on.
1. Process events
Please refer to the official documents for details: Process events
error | Triggered when a child process cannot be copied, created, killed, or sent |
exit | This event is triggered when the child process exits |
close | Triggered when the child process's standard I / O stream terminates |
disconnect | Triggers when calling the disconnect() method in the parent or subprocess, and closes the IPC channel. |
2. Automatic restart
Analysis: obtain the number of computer cpu cores through os files to create the most working processes, and listen to the emit event of the working process. Once a process exits, start a new process immediately to ensure that there are always processes serving users in the whole cluster.
let fork = require('child_process').fork; let cpus = require('os').cpus(); let server = require('net').createServer(); server.listen(1337); let workers = {}; let createWorker = function (i) { let worker = fork(__dirname + `/worker${i}.js`); // Restart the new process on exit worker.on('exit', () => { console.log('Worker' + worker.pid + 'exited'); delete workers[worker.pid]; createWorker(); }) // Handle forwarding worker.send('server' + i, server); console.log('Create worker.pid = ' + worker.pid); }; for(let i = 0; i< cpus.length; i++) { createWorker(i); } // Let all working processes exit when the process exits process.on('exit', ()=>{ for(let pid in workers){ workers[pid].kill(); } })
- Suicide signal
Analysis: the process listens for uncapped exceptions. In case of uncapped exceptions, it sends a suicide signal to the main process. After receiving the signal, the main process knows that a process stops working, and then restarts a new process.
// Suicide signal process.on('uncaughtException', () => { process.send({act: 'suicide'}); // Stop receiving new signals worker.close(()=>{ // Exit the process after all existing connections are disconnected process.exit(1); }) });
3. Load balancing
Listening to the same port between multiple processes enables user requests to be distributed to multiple processes for processing, which enables CPU resources to be called completely. To prevent a process from working all the time and some processes from stalling, the default mechanism of Node is to adopt the preemptive strategy of the operating system. This strategy may cause load imbalance. Therefore, Node v0 A new strategy, round robin, is proposed. Its working mode is that the main process accepts the connection and sends it to the working process in turn. The distribution strategy is to select the i = (i + 1) mod n process to send the connection in N working processes each time.
4. Status sharing
We know that too much data should not be placed in the Node process, because it will increase the burden of garbage collection and affect the performance. At the same time, Node does not allow data sharing among multiple processes. However, it is often inevitable in actual business, so we need to use a scheme and mechanism to realize data sharing among multiple processes.
- Third party data storage: in database, disk file and cache service Redis
- Timed polling: each sub process polls the third party regularly
- Active notification: when the data changes, the main process notifies each sub process in turn
III Cluster module
Cluster is a mature module for processing multi-core CPU s in Node. In v0 Before 8, process was used_ Child. After that, using cluster can solve its problem well. It provides a relatively perfect API to deal with the robustness of the process.
Analysis: through cluster Ismaster determines whether it is the main process, and then creates a working process or a listener.
let cluster = require('cluster'); let http = require('http'); const { off } = require('process'); let numCPUs = require('os').cpus().length; if(cluster.isMaster){ for(let i = 0; i<numCPUs; i++){ console.log(i) cluster.fork(); } cluster.on('exit', (worker, code, signal)=>{ console.log('worker' + worker.process.pid + 'died'); }); }else{ http.createServer((req, res)=>{ res.writeHead(200); res.end('hellow world'+ process.pid + '\n'); }).listen(8000) } // cluster.setupMaster({ // exec: 'worker.js' // })
IV summary
The power of the group is powerful. Through the master-slave mode, the quality of the application has been improved to a higher level. In the design, each sub process should only do one thing and do one thing well. They are connected through inter process technology to combine simple into powerful.