This article is a summary of Shanghai Node Party 5.13. It is mainly a popular science direction, and there are only a few holes to step on.Copy the code

The content is mainly divided into three parts, the big touch can be directly pulled to the end of the article to see the conclusion:

  • Process: Describes processes and Process objects
  • Child_process: Introduces child processes & IPC and stomp pits
  • Cluster: Introduction to load implementation and stomp pits

1. Process

First is the process part, about the process we need to clear two concepts, respectively: 1) the operating system’s process. 2) Node.js process object.

  • 1) Processes of the operating system

Operating system processes are a very basic concept on the server, so basic that it’s a little hard to introduce (laughs). We usually perceive processes through various tools, such as Unix ps command, Windows Tasklist command, etc.

~ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Apr22 ?        00:00:14 init
root         2     1  0 May07 ?        00:00:00 [kthreadd/4230]
root         3     2  0 May07 ?        00:00:00 [khelper/4230]
root       127     1  0 Apr22 ?        00:00:00 upstart-udev-bridge --daemon
root       145     1  0 Apr22 ?        00:00:01 /lib/systemd/systemd-udevd --daemon
syslog     302     1  0 Apr22 ?        00:01:00 rsyslogd
...
Copy the code

The meanings of some of these parameters:

& amp; amp; lt; img src=”https://pic2.zhimg.com/v2-78b459d39c39863a74d4be0c43f0a081_b.png” data-rawwidth=”1206″ data-rawheight=”507″ class=”origin_image zh-lightbox-thumb” width=”1206″ data-original=”https://pic2.zhimg.com/v2-78b459d39c39863a74d4be0c43f0a081_r.png”& amp; amp; gt; Other parameters are not listed, see details


ps

Simply put, a process is an instance of an application, and the same application can have multiple instances (processes). A process is a collection of system resources, including memory, CPU, etc. At the same time, the process is also the identification of the use of various resources in the system. Just like the id card can be used for a bank card, various resources such as FD and port are used for identification through the process.

PS: The operating system has a separate virtual memory space for each process to avoid cross-process memory injection problems.

  • 2) Process object

The Node.js process object is a collection of information and operations. It may be that the process function is binding in C++ (for the convenience of development), which leads to the mixing of many functions in process, including but not limited to:

  • Basic Process Information
  • Process the Usage
  • Process level event
  • System Account Information
  • The environment variable
  • Signal to send and receive
  • Three standard flows
  • Node.js depends on module/version information
  • .

You can even find ways to manipulate asynchrons on process objects, such as process.nexttick, so let’s talk about it now that nextTick is mentioned.

The official blog of Node.js (The Node.js Event Loop, Timers, and Process.nexttick ()) provides The official version of The Event Loop

Timers like setTimeout are handled centrally in the Timers segment, while process.nextTick is inserted after the end of each segment. So this blocks the entire Event Loop:

function test() { 
  process.nextTick(() => test());
}
Copy the code

And that doesn’t:

function test() { 
  setTimeout(() => test(), 0);
}
Copy the code

Also, since environment variables will be mentioned later, I will insert the context of environment variables as well. Setting environment variables can be obtained by using process.env. Node.js is often used for configuration, but it can also be obtained by reading a defined configuration file. There are many good libraries for this, such as Dotenv, Node-config, etc. According to RisingStack, node.js developers in 2016 were at the following rate when it came to configuration:

When asked “What configuration mode is currently used for node.js development by Ele. me”, the answer is “Both”. We read the configuration using a configuration file (JSON) and have a configuration template that will be generated and used when building in our CI system based on the environment variables of different environments.

2. Child Process

The child_process module is divided into three parts:

  • Exec: Starts a child process to execute the command, and calls bash to interpret the command, so if there are commands that have external arguments, you need to be aware of being injected.
  • Spawn: Safer start of a child process to execute commands, passing in various arguments using option to set the child process’s stdin, stdout, etc. Establish IPC communication with child processes through built-in pipes.
  • The special case of fork: spawn, which is used to produce workers or worker pools. The return value is that the ChildProcess object can easily interact with the ChildProcess.

A more detailed description of each interface can be found here. If you’re familiar with Unix development, you might ask:

Child_process. Fork and POSIX
forkWhat’s the difference?

  • IPC

Wikipedia lists

IPC is implemented in Node.js, named pipes in Windows, and Unix Domain Sockets in Unix (see official documentation). In theory, UDS is much faster than TCP sockets (note this flag later).

How do parent and child processes communicate before the built-in IPC is established?

When node.js starts the child process, the main process first establishes the IPC channel, and then passes the FD (file descriptor) of the IPC channel to the child process through the process.env environment variable (NODE_CHANNEL_FD). The child process then establishes a connection with the parent process by fd connecting to IPC.

process.js#L230

function setupChannel() { // If we were spawned with env NODE_CHANNEL_FD then load that up and // start parsing data from that stream. if (process.env.NODE_CHANNEL_FD) { const fd = parseInt(process.env.NODE_CHANNEL_FD, 10); assert(fd >= 0); // Make sure it's not accidentally inherited by child processes. delete process.env.NODE_CHANNEL_FD; const cp = require('child_process'); // Load tcp_wrap to avoid situation where we might immediately receive // a message. // FIXME is this really necessary? process.binding('tcp_wrap'); cp._forkChild(fd); assert(process.send); }}Copy the code

child_process.js#L103

exports._forkChild = function(fd) { // set process.send() var p = new Pipe(true); p.open(fd); p.unref(); const control = setupChannel(process, p); process.on('newListener', function onNewListener(name) { if (name === 'message' || name === 'disconnect') control.ref();  }); process.on('removeListener', function onRemoveListener(name) { if (name === 'message' || name === 'disconnect') control.unref(); }); };Copy the code

  • Node.js built-in IPC issues

As mentioned above, NODE.js IPC is based on UDS on Unix. Since UDS does not use the network underlying protocol to communicate, it bypats a lot of security checks and other issues, so it has the flag that the speed should be quite fast in theory. However, in practice, we find that node.js built-in IPC has great performance problems.

Here is a simple test to send data from the main process to child processes via node. js built-in IPC, 100 data per copy, each data size 1MB:

master.js

const child_process = require('child_process');

let child = child_process.fork('./child.js');
let data = Array(1024 * 1024).fill('0').join('');

setInterval(() => {
  let i = 100;
  while(i--) child.send(`${data}|${Date.now()}`);
}, 1000);
Copy the code

child.js

let i = 0;

process.on('message', (str) => {
  let now = Date.now();
  let [data, time] = str.split('|')
  console.log(i++, now - Number(time));
});
Copy the code

The test found that (MBP 2.7GHz I5, Node.js V7.6) was slow. The code is above, you can feel for yourself. The same function with TCP socket implementation speed can be many times worse (see the figure above), not posted data because the speed of IPC is unstable, will fluctuate in a relatively large range.

In short, node.js built-in IPC may have performance problems when transmitting large data (such as more than 1MB, without detailed analysis of the lower limit) due to implementation problems (C code has not been deeply studied), so it is not recommended to use it. If there are frequent data exchanges between processes, other solutions are recommended, such as socket communication, MQ transfer (Kafka has implemented a certain degree of real-time), RPC (Thrift, GRPC), and so on.

Party small partners asked which way we are currently using for development. The answer is self-built IPC communication with MQ and socket.

In addition to this flag, there is also a flag that uses a custom IPC to handle child process recycling. During online deployment and maintenance, the swap memory of the machine was full. The investigation found that there was a case in multi-process mode where the master died and the process was not notified to the worker to terminate, so that the worker became an orphan and the process was adopted by the system init. When there was no request for a long time, the worker’s memory was folded into swap memory.

When creating a child process using Node.js, you would normally only think of.on listening to the exit of the child process, and rarely consider the crash of the.on parent process.

To put it simply, executing a wait to retrieve child processes is a hassle, but it is designed to handle when the master hangs or does not collect. Node.js’s child implementation via IPC hides this detail, making it much easier to deal with.

Consider doing health checks on child processes as well. Keep a heartbeat between the child process and its parent process. If the heartbeat is broken, let the child process do some recycling and then gracefully call process.exit. Or consider using a tool like ZooKeeper to store information about each node, or you can systematically keep track of all nodes.

3. Cluster

The Node.js cluster module feels virtual to use. Because cluster is based on child_process.fork, communication between multiple workers is also through the built-in IPC. Let’s look at a simple code:

const cluster = require('cluster'); // | | const http = require('http'); // | | const numCPUs = require('os').cpus().length; / / | | are carried out / / | | the if (cluster. IsMaster) {/ / | - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / Fork workers. / / | for (var I = 0; i < numCPUs; i++) { // | cluster.fork(); / / / / | |} the parent only execute cluster. On (' exit '(worker) = > {/ / |. The console log (` ${worker. Process. Pid} died `); / / |}); // | } else { // |------------------- // Workers can share any TCP connection // | // In this case it is an HTTP server // | http.createServer((req, res) => { // | res.writeHead(200); / / | child only execute res. The end (' hello world \ n '); // | }).listen(8000); // | } // |------------------- // | | console.log('hello'); / / | | are carried outCopy the code

The reason why many students use cluster is probably that multiple workers can monitor the same port. In fact, the flags such as SO_REUSEADDR on the TCP layer are not exposed. In fact, this cluster can make multiple workers process the request of the same port, which does a lot of work. Next, we will briefly discuss the load balancing situation of the cluster.

In Node.js, load balancing (LB) is implemented in two ways: ① Handle sharing (WIN) and ② round-robin (* NIx).

① Handle sharing is mainly used in Windows. Specifically, after the main process creates the socket listening port, the socket handle is directly distributed to the interested worker, and then when the connection comes in, the worker directly accepts and processes it. Theoretically, this method should have the best performance, but in practice, there is a relatively large problem of uneven distribution (a common situation is that there are 8 workers and 70% of the connections are established with 2 of them).

Round-robin is the default on all platforms except Windows. The main process listens on the port, and after receiving the new connection, decides to pass the socket handle of the received client to the specified worker for processing through the time slice rotation method. As for which worker handles each connection, it is entirely determined by the built-in loop algorithm.

This round-robin balancing is a bit better than handle sharing, but I would say it’s better to use nginx with upstreams. It is worth mentioning that Node also performs better than Cluster by applying one port per process. Find that some students misunderstood, the schematic diagram:

Nginx is recommended if you want a decent common LB, and HAproxy is recommended if you want a good balance (but can’t handle static files like Nginx does). If you want LB to cache better than Nginx you can use Varnish (of course CDN is best for resource caching). In addition, if you want to have no awareness of the application of LB, you can find operation and maintenance support, such as DNS to LB (the disadvantage is that there is a delay), strong operation and maintenance can also do LB on the network card and so on.

summary

  • The Process object has a lot of functionality, and many of the details need to be understood.
  • Child_process: IPC is a little bit messy. Including transmission performance, as well as stability needs attention.
  • Cluster: The built-in LB is not recommended.

Finally, commercial time. This article is organized from the node-interview process. Welcome to Github to help fill in the gaps.