• No results found

7-3 Clustering with Node.js Problem

In document Node js Recipes Cory Gackenheimer pdf (Page 172-175)

You want to build a cluster of processes to run your application more efficiently.

Solution

Node.js provides a solution for clustering. This feature is still experimental at the time of this writing but it is capable of turning your single threaded Node.js application into one that utilizes multiple cores on your machine. In this way you are able to delegate Node.js tasks to separate threads, allowing for greater scalability. In this solution you will generate a Node.js application that will use the cluster module. The first example is a self-contained solution that will split a simple HTTP server and log the results of various cluster methods to your console.

Listing 7-7. Clustering /**

* Clustering */

var cluster = require('cluster'), http = require('http'),

cpuCount = require('os').cpus().length;

Method (buffer.<method>)

Description

writeUInt32BE(value, offset, [noassert]) Writes an unsigned 32-bit integer to the buffer, starting at the offset, big endian.

writeInt8(value, offset, [noassert]) Writes an 8-bit integer to the buffer, starting at the offset.

writeInt16LE(value, offset, [noassert]) Writes a 16-bit integer to the buffer, starting at the offset, little endian. writeInt16BE(value, offset, [noassert]) Writes a 16-bit integer to the buffer, starting at the offset, big endian. writeInt32LE(value, offset, [noassert]) Writes a 32-bit integer to the buffer, starting at the offset, little endian. writeInt32BE(value, offset, [noassert]) Writes a 32-bit integer to the buffer, starting at the offset, big endian. writeFloatLE(value, offset, [noassert]) Writes a float value to the buffer, starting at the offset, little endian. siwriteFloatBE(value, offset, [noassert]) Writes a float value to the buffer, starting at the offset, big endian. writeDoubleLE(value, offset, [noassert]) Writes a double value to the buffer, starting at the offset, little endian. writeDoubleBE(value, offset, [noassert]) Writes a double value to the buffer, starting at the offset, big endian. fill(value, [offset], [end]) Fills the buffer with the value specified from the offset to end range. Table 7-2. (continued)

Chapter 7 ■ DisCovering other noDe.js MoDules

if (cluster.isMaster) {

for (var i = 0; i < cpuCount; i++) { cluster.fork();

}

cluster.on('fork', function(worker) {

console.log(worker + ' worker is forked'); });

cluster.on('listening', function(worker, address) { console.log(worker + ' is listening on ' + address); }); cluster.on('online', function(worker) { console.log(worker + ' is online'); }); cluster.on('disconnect', function(worker) { console.log(worker + ' disconnected'); });

cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); });

} else {

// Workers can share any TCP connection // In this case it is an HTTP server http.createServer(function(req, res) { res.writeHead(200);

res.end("hello world\n"); }).listen(8000);

}

Now you will configure the cluster to execute a second Node.js file, once for each core on your machine. Listing 7-8. Clustering a Node.js Process

/**

* Clustering */

var cluster = require('cluster'),

cpuCount = require('os').cpus().length; cluster.setupMaster({

exec: '7-3-3.js' });

if (cluster.isMaster) {

for (var i = 0; i < cpuCount; i++) { cluster.fork();

}

cluster.on('fork', function(worker) {

console.log(worker + ' worker is forked'); });

Chapter 7 ■ DisCovering other noDe.js MoDules

cluster.on('listening', function(worker, address) {

console.log(worker + ' is listening on ' + address); }); cluster.on('online', function(worker) { console.log(worker + ' is online'); }); cluster.on('disconnect', function(worker) { console.log(worker + ' disconnected'); });

cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); });

}

Listing 7-9, the worker process, is shown here. Listing 7-9. The Worker Process

var http = require('http'); http.createServer(function(req, res) { console.log(req.url); res.writeHead(200); res.end("hello world\n"); }).listen(8000);

How It Works

Clustering in Node.js is essentially a solution to utilize one Node.js module and to split worker processes, utilizing the child_process.fork() function, all while maintaining reference and communication between the master and worker processes. The workers can be TCP or HTTP servers, and the requests are handled by the master process. This master process then utilizes round-robin load balancing to distribute the load through the server. It does this by listening for a connection, then calling a distribute method and handing off the processing to the worker process.

Listing 7-10. Master Listening, Then Distributing Load this.server.once('listening', function() { self.handle = self.server._handle; self.handle.onconnection = self.distribute.bind(self); self.server._handle = null; self.server = null; }); RoundRobinHandle.prototype.distribute = function(handle) { this.handles.push(handle);

var worker = this.free.shift(); if (worker) this.handoff(worker); };

Chapter 7 ■ DisCovering other noDe.js MoDules

RoundRobinHandle.prototype.handoff = function(worker) { if (worker.id in this.all === false) {

return; // Worker is closing (or has closed) the server. }

var handle = this.handles.shift(); if (typeof handle === 'undefined') {

this.free.push(worker); // Add to ready queue again. return;

}

var message = { act: 'newconn', key: this.key }; var self = this;

sendHelper(worker.process, message, handle, function(reply) { if (reply.accepted)

handle.close(); else

self.distribute(handle); // Worker is shutting down. Send to another. self.handoff(worker);

}); };

In your solutions you are working with the master process primarily. In both cases you have a simple HTTP server that will respond with a ‘hello world’ when a request is made to the server address. After importing the cluster module, you then check to see if the process is the cluster’s master process by using cluster.isMaster. You now check to see how many clusters you should make on your machine by checking the number of cores that your machine has using the ‘os’ module. For each CPU, you fork a new worker process calling cluster.fork(). Since the underlying framework still generates a new child process, which is still a new instance of v8, you can assume that the startup time for each worker will be greater than 0 and less than 100 ms in most cases. It will also generate approximately 10 MB of memory consumption per process at startup.

You now know that this is the master process, so you are able to bind to events that will be communicated to the master process. The events of interest are ‘fork’, ‘listening’, ‘exit’, ‘online’, and ‘setup’. The ‘fork’ event is emitted when a worker process is successfully forked and provides the worker object for the process that was forked. The ‘online’ event happens as soon as the forked process is created and running. The ‘listening’ event is sent once the worker has started listening. The ‘exit’ event is emitted when the worker process dies. If this happens you may want to call the .fork() method again to replace the downed worker.

Cluster is a powerful module that will allow you to distribute the load of your servers between multiple processes on your machine. As your Node.js application grows, this functionality can quickly become important in creating a scalable application.

7-4. Working with Query Strings

In document Node js Recipes Cory Gackenheimer pdf (Page 172-175)