The Cluster API at its core is extremely simple, all we need to do is pass
our tcp or http server
to cluster()
, then call listen()
as we would on the http.Server
itself.
var cluster = require('../')
, http = require('http');
var server = http.createServer(function(req, res){
res.writeHead(200);
res.end('Hello World');
});
cluster(server)
.listen(3000);
Alternatively (and recommended) is to export your server instance via module.exports
, and supply a path to cluster()
. For example app.js:
module.exports = http.createServer(....);
and server.js with our cluster logic, allowing our server to be require()
ed within tests, and preventing potential issues by having open database connections etc within the master processes, as only the workers need access to the server
instance.
cluster('app')
.listen(3000);
A good example if this, is a long-lived database connection. Our app.js may have this initialized at the top, which although will work fine stand-alone, may cause cluster's master processes to hang when restarting or closing due to the connection remaining active in the event loop.
var db = redis.createClient();
Cluster is not bound to servers, cluster can be used to manage processes for processing job queues etc. Below is a minimalist example of this, simply invokes cluster()
with no object, spawning a worker per cpu:
var cluster = require('cluster');
var proc = cluster().start();
if (proc.isWorker) {
// do things within the worker processes
} else {
// do something within the master
}
A plugin simple a function that accepts the master
process. Most plugin functions return another anonymous function, allowing them to accept options, for example:
function myPlugin(path){
return function(master) {
// do stuff
}
}
To use them, all we need to do is pass it to the use()
method:
cluster(server)
.use(myPlugin('/some/path'))
.listen(3000);
To use a plugin that is bundled with Cluster simply grab it from the cluster
object:
cluster(server)
.use(cluster.logger())
.listen(3000);
Below are the settings available:
workers
Number of workers to spawn, defaults to the number of CPUs or 1
working directory
Working directory defaulting to the script's dirbacklog
Connection backlog, defaulting to 128socket port
Master socket port defaulting to 8989
timeout
Worker shutdown timeout in milliseconds, defaulting to 60000
title
master process title defaulting to "cluster"worker title
worker process title defaulting to "cluster worker"user
User id / namegroup
Group id / nameWe can take what we have now, and go on to apply settings using the set(option, value)
method. For example:
cluster(server)
.set('working directory', '/')
.set('workers', 5)
.listen(3000);
Cluster performs the following actions when handling signals:
SIGINT
hard shutdownSIGTERM
hard shutdownSIGQUIT
graceful shutdownSIGUSR2
restart workersThe following events are emitted, useful for plugins or general purpose logging etc.
start
. When the IPC server is preppedworker
. When a worker is spawned, passing the worker
listening
. When the server is listening for connectionsclosing
. When master is shutting downclose
. When master has completed shutting downworker killed
. When a worker has diedworker exception
. Worker uncaughtException. Receives the worker and exception objectkill
. When a signal
is being sent to all workersrestarting
. Restart requested by REPL or signal. Receives an object
which can be patched in order to preserve plugin state.restart
. Restart complete, new master established, previous killed.
Receives an object with state preserved by the restarting
even,
patched in the previous master.Current state of the master process, one of:
active
hard shutdown
graceful shutdown
true
when the script is executed as a worker.
cluster = cluster(server).listen(3000);
if (cluster.isWorker) {
// do something
}
Alternatively we can use the CLUSTER_WORKER env var, populated with the worker's id.
true
when the script is executed as master.
cluster = cluster(server).listen(3000);
if (cluster.isMaster) {
// do something
}
Set option
to value
.
Register a plugin
for use.
Conditionally perform the following action, if
NODE_ENV matches env
.
cluster(server)
.in('development').use(cluster.debug())
.in('development').listen(3000)
.in('production').listen(80);
The environment conditionals may be applied to several calls:
cluster(server)
.set('working directory', '/')
.in('development')
.set('workers', 1)
.use(cluster.logger('logs', 'debug'))
.use(cluster.debug())
.listen(3000)
.in('production')
.set('workers', 4)
.use(cluster.logger())
.use(cluster.pidfiles())
.listen(80);
If we perform the same action for environments, set them before
the first in()
call, or use in('all')
.
cluster(server)
.set('working directory', '/')
.do(function(){
console.log('some arbitrary action');
})
.in('development')
.set('workers', 1)
.use(cluster.logger('logs', 'debug'))
.use(cluster.debug())
.in('production')
.set('workers', 4)
.use(cluster.logger())
.use(cluster.pidfiles())
.in('all')
.listen(80);
Spawn n
additional workers.
Graceful shutdown, waits for all workers to reply before exiting.
Hard shutdown, immediately kill all workers.
Defaults to a graceful restart, spawning a new master process, and sending SIGQUIT to the previous master process. Alternatively a custom signal
may be passed.
Sends SIGTERM or signal
to all worker processes. This method is used by Master#restart()
, Master#close()
etc.