9
Scaling Applications Using Multiple Processors in Node.js

In Chapter 4, “Using Events, Listeners, Timers, and Callbacks in Node.js,” you learned that Node.js applications run on a single thread rather than multiple threads. Using the single thread for application processing makes Node.js processes more efficient and faster. But most servers have multiple processors, and you can scale your Node.js applications by taking advantage of them. Node.js allows you to fork work from the main application to separate processes that can then be processed in parallel with each other and the main application.

To facilitate using multiple processes Node.js provides three specific modules. The process module provides access to the running processes. The child_process module provides the ability to create child processes and communicate with them. The cluster module implements clustered servers that share the same port, thus allowing multiple requests to be handled simultaneously.

Understanding the Process Module

The process module is a global object that can be accessed from your Node.js applications without the need to use a require(). This object gives you access to the running processes as well as information about the underlying hardware architecture.

Understanding Process I/O Pipes

The process module provides access to the standard I/O pipes for the process stdin, stdout, and stderr. stdin is the standard input pipe for the process, which is typically the console. You can read input from the console using the following code:

process.stdin.on('data', function(data){
  console.log("Console Input: " + data);
});

When you type in data to the console and press Enter, the data is written back out. For example:

some data
Console Input: some data

The stdout and stderr attributes of the process module are Writable streams that can be treated accordingly.

Understanding Process Signals

A great feature of the process module is that it allows you to register listeners to handle signals sent to the process from the OS. This is helpful when you need to perform certain actions, such as clean up before a process is stopped or terminated. Table 9.1 lists the process events that you can add listeners for.

To register for a process signal, simply use the on(event, callback) method. For example, to register an event handler for the SIGBREAK event, you would use the following code:

process.on('SIGBREAK', function(){
  console.log("Got a SIGBREAK");
});

Table 9.1 Events that can be sent to Node.js processes

Event

Description

SIGUSR1

Emitted when the Node.js debugger is started. You can add a listener; however, you cannot stop the debugger from starting.

SIGPIPE

Emitted when the process tries to write to a pipe without a process connected on the other end.

SIGHUP

Emitted on Windows when the console window is closed, and on other platforms under various similar conditions. Note: Windows terminates Node.js about 10 seconds after sending this event.

SIGTERM

Emitted when a request is made to terminate the process. This is not supported on Windows.

SIGINT

Emitted when a Break is sent to the process. For example, when Ctrl+C is pressed.

SIGBREAK

Emitted on Windows when Ctrl+Break is pressed.

SIGWINCH

Emitted when the console has been resized. On Windows, this is emitted only when you write to the console, when the cursor is being moved, or when a readable TTY is used in raw mode.

SIGKILL

Emitted on a process kill. Cannot have a listener installed.

SIGSTOP

Emitted on a process stop. Cannot have a listener installed.

Controlling Process Execution with the process Module

The process module also gives you some control over the execution of processes, specifically, the ability to stop the current process, kill another process, or schedule work to run on the event queue. These methods are attached directly to the process module. For example, to exit the current Node.js process, you would use:

process.exit(0)

Table 9.2 lists the available process control methods on the process module.

Table 9.2 Methods that can be called on the process module to affect process execution

Method

Description

abort()

Causes the current Node.js application to emit an abort event, exit, and generate a memory core.

exit([code])

Causes the current Node.js application to exit and return the specified code.

kill(pid, [signal])

Causes the OS to send a kill signal to the process with the specified pid. The default signal is SIGTERM, but you can specify another.

nextTick(callback)

Schedules the callback function on the Node.js application’s queue.

Getting Information from the process Module

The process module provides a wealth of information about the running process and the system architecture. This information can be useful when implementing your applications. For example, the process.pid property gives you the process ID that can then be used by your application.

Table 9.3 lists the properties and methods that you can access from the process module and describes what they return.

Table 9.3 Methods that can be called on the process module to gather information

Method

Description

version

Specifies the version of Node.js.

versions

Provides an object containing the required modules and version for this Node.js application.

config

Contains the configuration options used to compile the current node executable.

argv

Contains the command arguments used to start the Node.js application. The first element is the node, and the second element is the path to the main JavaScript file.

execPath

Specifies the absolute path where Node.js was started from.

execArgv

Specifies the node-specific command-line options used to start the application.

chdir(directory)

Changes the current working directory for the application. This can be useful if you provide a configuration file that is loaded after the application has started.

cwd()

Returns the current working directory for the process.

env

Contains the key/value pairs specified in the environment for the process.

pid

Specifies the current process’s ID.

title

Specifies the title of the currently running process.

arch

Specifies the processor architecture the process is running on (for example, x64, ia32, or arm).

platform

Specifies the OS platform (for example, linux, win32, or freebsd).

memoryUsage()

Describes the current memory usage of the Node.js process. You need to use the util.inspect() method to read in the object. For example:

console.log(util.inspect(process.memoryUsage()));{ rss: 13946880, heapTotal: 4083456, heapUsed: 2190800 }

maxTickDepth

Specifies the maximum number of events schedule by nextTick() that will be run before allowing blocking I/O events from being processed. You should adjust this value as necessary to keep your I/O processes from being starved.

uptime()

Contains the number of seconds the Node.js processor has been running.

hrtime()

Returns a high-resolution time in a tuple array [seconds, nanoseconds]. This can be used if you need to implement a granular timing mechanism.

getgid()

On POSIX platforms, returns the numerical group ID for this process.

setgid(id)

On POSIX platforms, sets the numerical group ID for this process.

getuid()

On POSIX platforms, returns the numerical or string user ID for this process.

setuid(id)

On POSIX platforms, sets the numerical or string user ID for this process.

getgroups()

On POSIX platforms, returns an array of group IDs.

setgroups(groups)

On POSIX platforms, sets the supplementary group IDs. Your Node.js application needs root privileges to call this method.

initgroups(user, extra_group)

On POSIX platforms, initializes the group access list with the information from /etc/group. Your Node.js application needs root privileges to call this method.

To help you understand accessing information using the process module, Listing 9.1 makes a series of calls and outputs the results to the console, as shown in Listing 9.1 Output.

Listing 9.1 process_info.js: Accessing information about the process and system using the process module

01 var util = require('util');
02 console.log('Current directory: ' + process.cwd());
03 console.log('Environment Settings: ' + JSON.stringify(process.env));
04 console.log('Node Args: ' + process.argv);
05 console.log('Execution Path: ' + process.execPath);
06 console.log('Execution Args: ' + JSON.stringify(process.execArgv));
07 console.log('Node Version: ' + process.version);
08 console.log('Module Versions: ' +  JSON.stringify(process.versions));
09 //console.log(process.config);
10 console.log('Process ID: ' + process.pid);
11 console.log('Process Title: ' + process.title);
12 console.log('Process Platform: ' + process.platform);
13 console.log('Process Architecture: ' + process.arch);
14 console.log('Memory Usage: ' + util.inspect(process.memoryUsage()));
15 var start = process.hrtime();
16 setTimeout(function() {
17   var delta = process.hrtime(start);
18   console.log('High-Res timer took %d seconds and %d nanoseconds', delta[0], + delta[1]);
19   console.log('Node has been running %d seconds', process.uptime());
20 }, 1000);

Listing 9.1 Output Accessing information about the process and system using the process module

Current directory: C:UsersCalebTZDworkspace
odecodech09
Environment Settings:
Node Args: C:Program Files
odejs
ode.exe,C:UsersCalebTZDworkspace
odecodech09process_info.js
Execution Path: C:Program Files
odejs
ode.exe
Execution Args: []
Node Version: v7.8.0
Module Versions: Node Config:
Process ID: 12896
Process Title: C:Program Files
odejs
ode.exe
Process Platform: win32
Process Architecture: x64
Memory Usage: { rss: 20054016,
  heapTotal: 5685248,
  heapUsed: 3571496,
  external: 8772 }
High-Res timer took 1 seconds and 913430 nanoseconds
Node has been running 1.123 seconds

Implementing Child Processes

To take advantage of multiple processors in a server with your Node.js applications, you need to farm work off to child processes. Node.js provides the child_process module that allows you to spawn, fork, and execute work on other processes. The following sections discuss the process of executing tasks on other processes.

Keep in mind that child processes do not have direct access to the global memory in each other or the parent process. Therefore, you need to design your applications to run in parallel.

Understanding the ChildProcess Object

The child_process module provides a new class called ChildProcess that acts as a representation of the child processes that can be accessed from the parent. This allows you to control, end, and send messages to the child processes from the parent process that started them.

The process module is a ChildProcess object as well. This means that when you access process from the parent module, it is the parent ChildProcess object, but when you access process from the child process, it is the ChildProcess object.

The purpose of this section is to familiarize you with the ChildProcess object so that in subsequent sections you can actually implement multiprocess Node.js applications. The best way to do that is to learn about the events, attributes, and methods of the ChildProcess object.

Table 9.4 lists the events that can be emitted on the ChildProcess object. You implement handlers for the events to handle when the child process terminates or sends messages back to the parent.

Table 9.4 Events that can be emitted on ChildProcess objects

Event

Description

message

Emitted when a ChildProcess object calls the send() method to send data. Listeners on this event implement a callback that can then read the data sent. For example:

child.on('send': function(message){console.log(message});

error

Emitted when an error occurs in the worker. The handler receives an error object as the only parameter.

exit

Emitted when a worker process ends. The handler receives two arguments, code and signal, that specify the exit code and the signal passed to kill the process if it was killed by the parent.

close

Emitted when all the stdio streams of a worker process have terminated. Different from exit because multiple processes might share the same stdio streams.

disconnect

Emitted when disconnect() is called on a worker.

Table 9.5 lists the methods that can be called on the child process. These methods allow you to terminate, disconnect, or send messages to the child process. For example, the following code can be called from the parent process to send an object to the child process:

child.send({cmd: 'command data'});

Table 9.5 Methods that can be called on ChildProcess objects

Method

Description

kill([signal])

Causes the OS to send a kill signal to the child process. The default signal is SIGTERM, but you can specify another. See Table 9.1 for a list of signal strings.

send(message, [sendHandle])

Sends a message to the handle. The message can be a string or an object. The optional sendHandle parameter allows you to send a TCP Server or Socket object to the client. This allows the client process to share the same port and address.

disconnect()

Closes the IPC channel between the parent and child and sets the connected flag to false in both the parent and child processes.

Table 9.6 lists the properties that you can access on a ChildProcess object.

Table 9.6 Properties that can be accessed on ChildProcess objects

Property

Description

stdin

An input Writable stream.

stdout

A standard output Readable stream.

stderr

A standard output Readable stream for errors.

pid

An ID of the process.

connected

A Boolean that is set to false after disconnect() is called. When this is false, you can no longer send() messages to the child.

Executing a System Command on Another Process Using exec()

The simplest method of adding work to another process from a Node.js process is to execute a system command in a subshell using the exec() function. The exec() function can execute just about anything that can be executed from a console prompt; for example, a binary executable, shell script, Python script, or batch file.

When executed, the exec() function creates a system subshell and then executes a command string in that shell just as if you had executed it from a console prompt. This has the advantage of being able to leverage the capabilities of a console shell, such as accessing environment variables on the command line.

The syntax for the exec() function call is shown below. The execFile() function call returns a ChildProcess object:

child_process.exec(command, [options], callback)

The command parameter is a string that specifies the command to execute in the subshell. The options parameter is an object that specifies settings to use when executing the command, such as the current working directory. Table 9.7 lists the options that can be specified by the exec() command.

The callback parameter is a function that accepts three parameters: error, stdout, and stderr. The error parameter is passed an error object if an error is encountered when executing the command. stdout and stderr are Buffer objects that contain the output from executing the command.

Table 9.7 Options that can be set when using the exec() and execFile() Functions

Property

Description

cwd

Specifies the current working directory for the child process to execute within.

env

Object whose property:value pairs are used as environment key/value pairs.

encoding

Specifies the encoding to use for the output buffers when storing output from the command.

maxBuffer

Specifies the size of the output buffers for stdout and stderr. The default value is 200*1024.

timeout

Specifies the number of milliseconds for the parent process to wait before killing the child process if it has not completed. The default is 0, which means there is no timeout.

killSignal

Specifies the kill signal to use when terminating the child process. The default is SIGTERM.

The code in Listing 9.2 illustrates an example of executing a system command using the exec() function. Listing 9.2 Output shows the result.

Listing 9.2 child_exec.js: Executing a system command in another process

01 var childProcess = require('child_process');
02 var options = {maxBuffer:100*1024, encoding:'utf8', timeout:5000};
03 var child = childProcess.exec('dir /B', options,
04                               function (error, stdout, stderr) {
05   if (error) {
06     console.log(error.stack);
07     console.log('Error Code: '+error.code);
08     console.log('Error Signal: '+error.signal);
09   }
10   console.log('Results: 
' + stdout);
11   if (stderr.length){
12     console.log('Errors: ' + stderr);
13   }
14 });
15 child.on('exit', function (code) {
16   console.log('Completed with code: '+code);
17 });

Listing 9.2 Output child_exec.js: Executing a system command in another process

Completed with code: 0
Results:
chef.js
child_fork.js
child_process_exec.js
child_process_exec_file.js
child_process_spawn.js
cluster_client.js
cluster_server.js
cluster_worker.js
file.txt
process_info.js

Executing an Executable File on Another Process Using execFile()

Another simple method of adding work to another process from a Node.js process is to execute an executable file on another process using the execFile() function. This is similar to using exec() except that no subshell is used. This makes execFile() lighter weight, but it also means that the command to execute must be a binary executable. Shell scripts on Linux and batch files on Windows do not work with the execFile() function.

The syntax for the execFile() function call is shown below. The execFile() function returns a ChildProcess object:

child_process.execFile(file, args, options, callback)

The file parameter is a string that specifies the path to the executable file that will be executed. The args parameter is an array that specifies command-line arguments to be passed to the executable. The options parameter is an object that specifies settings to use when executing the command, such as the current working directory. Table 9.7 lists the options that can be specified by the execFile() command.

The callback parameter is a function that accepts three parameters: error, stdout, and stderr. The error parameter is passed an error object if an error is encountered when executing the command. stdout and stderr are Buffer objects that contain the output from executing the command.

Listing 9.3 illustrates executing a system command using the execFile() function. Listing 9.3 Output shows the output.

Listing 9.3 child_process_exec_file.js: Executing an executable file in another process

01 var childProcess = require('child_process');
02 var options = {maxBuffer:100*1024, encoding:'utf8', timeout:5000};
03 var child = childProcess.execFile('ping.exe', ['-n', '1', 'google.com'],
04                             options, function (error, stdout, stderr) {
05   if (error) {
06     console.log(error.stack);
07     console.log('Error Code: '+error.code);
08     console.log('Error Signal: '+error.signal);
09   }
10   console.log('Results: 
' + stdout);
11   if (stderr.length){
12     console.log('Errors: ' + stderr);
13   }
14 });
15 child.on('exit', function (code) {
16   console.log('Child completed with code: '+code);
17 });

Listing 9.3 Output child_process_exec_file.js: Executing an executable file in another process

Child completed with code: 0
Results:
Pinging google.com [216.58.195.78] with 32 bytes of data:
Reply from 216.58.195.78: bytes=32 time=47ms TTL=55

Ping statistics for 216.58.195.78:
    Packets: Sent = 1, Received = 1, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 47ms, Maximum = 47ms, Average = 47ms

Spawning a Process in Another Node.js Instance Using spawn()

A more complex method of adding work to another process from a Node.js process is to spawn another process; link the stdio, stdout, and stderr pipes between them; and then execute a file on the new process using the spawn() function. That makes spawning a bit heavier than exec() but provides some great benefits.

The major differences between spawn() and exec()/execFile() are that the stdin for the spawned process can be configured and the stdout and stderr are Readable streams in the parent process. This means that exec() and execFile() must complete before reading the buffer outputs. However, you can read output data from a spawn() process as soon as it is written.

The syntax for the spawn() function call is shown below. The spawn() function returns a ChildProcess object:

child_process.spawn(command, [args], [options])

The command parameter is a string that specifies the command to be executed. The args parameter is an array that specifies command-line arguments to be passed to the executable command. The options parameter is an object that specifies settings to use when executing the command, such as the current working directory. Table 9.8 lists the options that can be specified by the spawn() command.

The callback parameter is a function that accepts three parameters: error, stdout, and stderr. The error parameter is passed an error object if an error is encountered when executing the command. The stdout and stderr are defined by the stdio option settings; by default they are Readable stream objects.

Table 9.8 Properties of the options parameter that can be set when using the spawn() function

Property

Description

cwd

A string representing the current working directory of the child process.

env

An object whose property:value pairs are used as environment key/value pairs.

detached

A Boolean; when true, this child process is made the leader of a new process group enabling the process to continue even when the parent exits. You should also use child.unref() so that the parent process does not wait for the child process before exiting.

uid

Specifies the user identity of the process for POSIX processes.

gid

Specifies the group identity of the process for POSIX processes.

stdio

An array that defines the child process stdio configuration ([stdin, stdout, stderr]). By default, Node.js opens file descriptors [0, 1, 2] for [stdin, stdout, stderr]. The strings define the configuration of each input and output stream. For example:
['ipc', 'ipc', 'ipc']

The following list describes each of the options that can be used:

'pipe': Creates a pipe between the child and parent process. The parent can access the pipe using ChildProcess.stdio[fd] where fd is the file descriptors [0, 1, 2] for [stdin, stdout, stderr].

'ipc': Creates an IPC channel for passing messages/file descriptors between the parent and child using the send() method described earlier.

'ignore': Does not set up a file descriptor in the child.

Stream object: Specifies a Readable or Writeable stream object defined in the parent to use. The Stream object’s underlying file descriptor is duplicated in the child and thus data can be streamed from child to parent and vice versa.

File Descriptor Integer: Specifies the integer value of a file descriptor to use.

null, undefined: Uses the defaults of [0, 1, 2] for the [stdin, stdout, stderr] values.

Listing 9.4 illustrates executing a system command using the spawn() function. Listing 9.4 Output shows the output.

Listing 9.4 child_process_spawn_file.js: Spawning a command in another process

01 var spawn = require('child_process').spawn;
02 var options = {
03     env: {user:'brad'},
04     detached:false,
05     stdio: ['pipe','pipe','pipe']
06 };
07 var child = spawn('netstat', ['-e']);
08 child.stdout.on('data', function(data) {
09   console.log(data.toString());
10 });
11 child.stderr.on('data', function(data) {
12   console.log(data.toString());
13 });
14 child.on('exit', function(code) {
15   console.log('Child exited with code', code);
16 });

Listing 9.4 Output child_process_spawn_file.js: Spawning a command in another process

Interface Statistics
                           Received            Sent
Bytes                     893521612       951835252
Unicast packets              780762         5253654
Non-unicast packets           94176           31358

Child exited with code 0
Discards                          0               0
Errors                            0               0
Unknown protocols                 0

Implementing Child Forks

Node.js also provides a specialized form of process spawning called a fork, which is designed to execute Node.js module code inside another V8 instance running on a separate processor. This has the advantage of allowing you to run multiple services in parallel. However, it also takes time to spin up a new instance of V8, and each instance takes about 10MB of memory. Therefore, you should design your forked processes to be longer lived, and not require many of them. Remember that you don’t get a performance benefit for creating more processes than you have CPUs in the system.

Unlike spawn, you cannot configure the stdio for the child process; instead it is expected that you use the send() mechanism in the ChildProcess object to communicate between the parent and child processes.

The syntax for the fork() function call is shown below. The fork() function returns a ChildProcess object:

child_process.fork(modulePath, [args], [options])

The modulePath parameter is a string that specifies the path to the JavaScript file that is launched by the new Node.js instance. The args parameter is an array that specifies command-line arguments to be passed to the node command. The options parameter is an object that specifies settings to use when executing the command, such as the current working directory. Table 9.9 lists the options that can be specified by the fork() command.

The callback parameter is a function that accepts three parameters: error, stdout, and stderr. The error parameter is passed an error object if an error is encountered when executing the command. The stdout and stderr are Readable stream objects.

Table 9.9 Properties of the options parameter that can be set when using the fork() function

Property

Description

cwd

A string representing the current working directory of the child process.

env

An object whose property:value pairs are used as environment key/value pairs.

encoding

Specifies the encoding to use when writing data to the output streams and across the send() IPC mechanism.

execPath

Specifies the executable to use to create the spawned Node.js process. This allows you to use different versions of Node.js for different processes, although that is not recommended in case the process functionality is different.

silent

A Boolean; when true, the stdout and stderror in the forked process are not associated with the parent process. The default is false.

Listing 9.5 and Listing 9.6 illustrate examples of forking work off to another Node.js instance running in a separate process. Listing 9.5 uses fork() to create three child processes running the code from Listing 9.6. The parent process then uses the ChildProcess objects to send commands to the child processes. Listing 9.6 implements the process.on('message') callback to receive messages from the parent, and the process.send() method to send the response back to the parent process, thus implementing the IPC mechanism between the two.

The output is shown in Listing 9.6 Output.

Listing 9.5 child_fork.js: A parent process creating three child processes and sending commands to each, executing in parallel

01 var child_process = require('child_process');
02 var options = {
03     env:{user:'Brad'},
04     encoding:'utf8'
05 };
06 function makeChild(){
07   var child = child_process.fork('chef.js', [], options);
08   child.on('message', function(message) {
09     console.log('Served: ' + message);
10   });
11   return child;
12 }
13 function sendCommand(child, command){
14   console.log("Requesting: " + command);
15   child.send({cmd:command});
16 }
17 var child1 = makeChild();
18 var child2 = makeChild();
19 var child3 = makeChild();
20 sendCommand(child1, "makeBreakfast");
21 sendCommand(child2, "makeLunch");
22 sendCommand(child3, "makeDinner");

Listing 9.6 chef.js: A child process handling message events and sending data back to the parent process

01 process.on('message', function(message, parent) {
02   var meal = {};
03   switch (message.cmd){
04     case 'makeBreakfast':
05     meal = ["ham", "eggs", "toast"];
06     break;
07     case 'makeLunch':
08     meal = ["burger", "fries", "shake"];
09     break;
10     case 'makeDinner':
11     meal = ["soup", "salad", "steak"];
12     break;
13   }
14   process.send(meal);
15 });

Listing 9.5 Output chef.js: A child process handling message events and sending data back to the parent process

Requesting: makeBreakfast
Requesting: makeLunch
Requesting: makeDinner
Served: soup,salad,steak
Served: ham,eggs,toast
Served: burger,fries,shake

Implementing Process Clusters

One of the coolest things you can do with Node.js is create a cluster of Node.js instances running in parallel in separate processes on the same machine. You can do that using the techniques you learned about the in the previous section by forking processes and then using the send(message, serverHandle) IPC mechanism to communicate send messages and pass the underlying TCP server handles between them. However, because that is such a common task, Node.js has provided the cluster module that does all that for you automatically.

Using the Cluster Module

The cluster module provides the functionality necessary to easily implement a cluster of TCP or HTTP servers running in different processes on the same machine but still using the same underlying socket, thus handling requests on the same IP address and port combination. The cluster module is simple to implement and provides several events, methods, and properties that can be used to initiate and monitor a cluster of Node.js servers.

Table 9.10 lists the events that can be emitted in a cluster application.

Table 9.10 Events that can be emitted by the cluster module

Event

Description

fork

Emitted when a new worker has been forked. The callback function receives a Worker object as the only argument. For example:

function (worker)

online

Emitted when the new worker sends back a message indicating that it has started. The callback function receives a Worker object as the only argument. For example:

function (worker)

listening

Emitted when the worker calls listen() to begin listening on the shared port. The callback handler receives the worker object as well as an address object indicating the port the worker is listening on. For example:

function (worker, address)

disconnect

Emitted after the IPC channel has been disconnected, such as the server calling worker.disconnect(). The callback function receives a Worker object as the only argument. For example:

function (worker)

exit

Emitted when the Worker object has disconnected. The callback handler receives the worker, exit code, and signal used. For example:

function (worker, code, signal)

setup

Emitted the first time the setupMaster() is called.

Table 9.11 lists the methods and properties available in the cluster module, allowing you to get information such as whether this node is a worker or the master as well as configuring and implementing the forked processes.

Table 9.11 Methods and properties of the cluster module

Property

Description

settings

Contains the exec, args, and silent property values used to set up the cluster.

isMaster

Is true if the current process is the cluster master; otherwise, it is false.

isWorker

Is true if the current process is a worker; otherwise, it is false.

setupMaster([settings])

Accepts an optional settings object that contains exec, args, and silent properties. The exec property points to the worker JavaScript file. The args property is an array of parameters to pass, and silent disconnects the IPC mechanism from the worker thread.

disconnect([callback])

Disconnects the IPC mechanism from the workers and closes the handles. The callback function is executed when the disconnect finishes.

worker

References the current Worker object in worker processes. This is not defined in the master process.

workers

Contains the Worker object, which you can reference by ID from the master process. For example:

cluster.workers[workerId]

Understanding the Worker Object

When a worker process is forked, a new Worker object is created in both the master and worker processes. In the worker process, the object is used to represent the current worker and interact with cluster events that are occurring. In the master process, the Worker object is used to represent child worker processes so that your master application can send messages to them, receive events on their state changes, and even kill them.

Table 9.12 lists the events that Worker objects can emit.

Table 9.12 Events that can be emitted by Worker objects

Event

Description

message

Emitted when the worker receives a new message. The callback function is passed the message as the only parameter.

disconnect

Emitted after the IPC channel has been disconnected on this worker.

exit

Emitted when this Worker object has disconnected.

error

Emitted when an error has occurred on this worker.

Table 9.13 lists the methods and properties available in the Worker object, allowing you to get information such as whether this node is a worker or the master as well as configuring and implementing the forked processes.

Table 9.13 Methods and properties of the Worker module

Property

Description

id

Represents the unique ID of this worker.

process

Specifies the ChildProcess object this worker is running on.

suicide

Is set to true when kill() or disconnect() is called on this worker. You can use this flag to determine whether you should break out of loops to try and go down gracefully.

send(message, [sendHandle])

Sends a message to the master process.

kill([signal])

Kills the current worker process by disconnecting the IPC channel and then exiting. Sets the suicide flag to true.

disconnect()

When called in the worker, closes all servers, waits for the close event, and then disconnects the IPC channel. When called from the master, sends an internal message to the worker causing it to disconnect itself. Sets the suicide flag.

Implementing an HTTP Cluster

The best way to illustrate the value of the cluster module is to show a basic implementation of Node.js HTTP servers. Listing 9.7 implements a basic cluster of HTTP servers. Lines 4–13 register listeners for the fork, listening, and exit events on cluster workers. Then in line 14 setupMaster() is called and the worker executable cluster_worker.js is specified. Next, lines 15–19 create the workers by calling cluster.fork(). Finally, in lines 20–24 the code iterates through the workers and registers an on('message') event handler for each one.

Listing 9.8 implements the worker HTTP servers. Notice that the http server sends back a response to the client and then also sends a message to the cluster master on line 7.

Listing 9.9 implements a simple HTTP client that sends a series of requests to test the servers created in Listing 9.8. The output of the servers is shown in Listing 9.7 and 9.8 Output, and the output of the clients is shown in Listing 9.9 Output. Notice that Listing 9.9 Output shows that the requests are being handled by different processes on the server.

Listing 9.7 cluster_server.js: A master process creating up to four worker processes

01 var cluster = require('cluster');
02 var http = require('http');
03 if (cluster.isMaster) {
04   cluster.on('fork', function(worker) {
05     console.log("Worker " + worker.id + " created");
06   });
07   cluster.on('listening', function(worker, address) {
08     console.log("Worker " + worker.id +" is listening on " +
09                 address.address + ":" + address.port);
10   });
11   cluster.on('exit', function(worker, code, signal) {
12     console.log("Worker " + worker.id + " Exited");
13   });
14   cluster.setupMaster({exec:'cluster_worker.js'});
15   var numCPUs = require('os').cpus().length;
16   for (var i = 0; i < numCPUs; i++) {
17     if (i>=4) break;
18     cluster.fork();
19   }
20   Object.keys(cluster.workers).forEach(function(id) {
21     cluster.workers[id].on('message', function(message){
22       console.log(message);
23     });
24   });
25 }

Listing 9.8 cluster_worker.js: A worker process implementing an HTTP server

01 var cluster = require('cluster');
02 var http = require('http');
03 if (cluster.isWorker) {
04   http.Server(function(req, res) {
05     res.writeHead(200);
06     res.end("Process " + process.pid + " says hello");
07     process.send("Process " + process.pid + " handled request");
08   }).listen(8080, function(){
09     console.log("Child Server Running on Process: " + process.pid);
10   });
11 };

Listing 9.9 cluster_client.js: An HTTP client sending a series of requests to test the server

01 var http = require('http');
02 var options = { port: '8080' };
03 function sendRequest(){
04   http.request(options, function(response){
05     var serverData = '';
06     response.on('data', function (chunk) {
07       serverData += chunk;
08     });
09     response.on('end', function () {
10       console.log(serverData);
11     });
12   }).end();
13 }
14 for (var i=0; i<5; i++){
15   console.log("Sending Request");
16   sendRequest();
17 }

Listing 9.7 and 9.8 Output cluster_server.js: A master process creating up to four worker processes

Worker 1 created
Worker 2 created
Worker 3 created
Worker 4 created
Child Server Running on Process: 9012
Worker 1 is listening on null:8080
Child Server Running on Process: 1264
Worker 2 is listening on null:8080
Child Server Running on Process: 5488
Worker 4 is listening on null:8080
Child Server Running on Process: 7384
Worker 3 is listening on null:8080
Process 1264 handled request
Process 7384 handled request
Process 5488 handled request
Process 7384 handled request
Process 5488 handled request

Listing 9.9 Output cluster_client.js: An HTTP client sending a series of requests to test the server

Sending Request
Sending Request
Sending Request
Sending Request
Sending Request
Process 10108 says hello
Process 12584 says hello
Process 13180 says hello
Process 10108 says hello
Process 12584 says hello

Summary

To make the most out of Node.js performance on servers with multiple processors, you need to be able to farm work off to the other processes. The process module allows you to interact with the system process, the child_process module allows you to actually execute code on a separate process, and the cluster module allows you to create a cluster of HTTP or TCP servers.

The child_process module provides the exec(), execFile(), spawn(), and fork() functions, which are used to start work on separate processes. The ChildProcess and Worker objects provide a mechanism to communicate between the parent and child processes.

Next

In the next chapter, you are introduced to some other modules that Node.js provides for convenience. For example, the os module provides tools to interact with the OS, and the util module provides useful functionality.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.44.118