集群
单个 Node 实例运行在单个线程中。要发挥多核系统的能力,用户有时候需要启动一个 Node 进程集群来处理负载。
集群模块允许你方便地创建一个共享服务器端口的进程网络。
var cluster = require(‘cluster‘);
var http = require(‘http‘);
var numCPUs = require(‘os‘).cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on(‘exit‘, function(worker, code, signal) {
console.log(‘worker ‘ + worker.process.pid + ‘ died‘);
});
} else {
// Workers can share any TCP connection
// In this case its a HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}
现在,运行 node 将会在所有工作进程间共享 8000 端口:
% NODE_DEBUG=cluster node server.js
23521,Master Worker 23524 online
23521,Master Worker 23526 online
23521,Master Worker 23523 online
23521,Master Worker 23528 online
这是一个近期推出的功能,在未来版本中可能会有所改变。请尝试并提供反馈。
还要注意的是,在
Windows 中尚不能在工作进程中建立一个被命名的管道服务器。
它是如何工作的
工作进程是通过使用 child_process.fork
方法派生的,因此它们可以通过
IPC(进程间通讯)与父进程通讯并互相传递服务器句柄。
集群模块支持两种分配传入连接的方式。
当你在一个工作进程中调用server.listen(...)
时,它序列化参数并且将请求传递给主进程。若主进程已经监听匹配工作进程的服务,它将向工作进程发送该句柄。若主进程没有监听配工作进程的服务,那么就创建该服务并将句柄发送给工作进程。
这种模式导致了在一下三种情况下令人惊讶的潜在行为:
server.listen({fd: 7})
由于消息被传递到主进程,父进程中的文件描述符 7 会被监听,并且句柄会被传递给工作进程,而不是监听工作进程中文件描述符 7 所引用的东西。server.listen(handle)
明确地监听一个句柄会使得工作进程使用所给句柄,而不是与主进程通讯。如果工作进程已经拥有了该句柄,则假定您知道您在做什么。server.listen(0)
通常,这会让服务器监听一个随机端口。然而,在集群中,各个工作进程每次listen(0)
都会得到一样的“随机”端口。实际上,端口在第一次时是随机的,但在那之后却是可预知的。如果您想要监听一个唯一的端口,则请根据集群工作进程
ID 来生成端口号。
当多个进程都在accept()同一潜在资源时,操作系统的负载均衡是非常有效的。由于在
Node.js 或您的程序中并没有路由逻辑,工作进程之间也没有共享的状态,因此在您的程序中,诸如会话和登录等功能应当被设计成不能太过依赖于内存中的数据对象。
由于工作进程都是独立的进程,因此它们会根据您的程序的需要被终止或重新派生,并且不会影响到其它工作进程。只要还有工作进程存在,服务器就会继续接受连接。但是,Node
不会自动为您管理工作进程的数量,根据您的程序所需管理工作进程池是您的责任。
cluster.settings
- {Object}
Array 列表传递给node可执行文件。(默认为exec
Argv将字符串参数process.execArgv
)exec
{String} 工作进程文件的路径。(默认为process.argv[1]
)
args
{Array} 传递给工作进程的字符串参数。(默认为process.argv.slice(2)
)silent
{Boolean} 是否将输出发送到父进程的 stdio。(默认为false
)
当完成调用 .setupMaster()
(或者 .fork()
)时,
这个setting对象将包含设置,包括默认值。
由于.setupMaster()
仅能被调一次,所以这种行为在设置后有效的冻结了函数。
这个对象不支持变更和手动设置。
cluster.isMaster
- {Boolean}
如果进程为主进程则为 true
。这是由 process.env.NODE_UNIQUE_ID
判断的,如果process.env.NODE_UNIQUE_ID
为
undefined,则 isMaster
为 true
。
cluster.isWorker
- Boolean
若非主进程为true。(与cluster.isMaster相反
)
Event: ‘fork‘
worker
Worker
object
当一个新的工作进程被分支出来,cluster 模块会发送一个 ‘fork‘ 事件。这可被用于记录工作进程活动,以及创建您自己的超时判断。
var timeouts = [];
function errorMsg() {
console.error("Something must be wrong with the connection ...");
}
cluster.on(‘fork‘, function(worker) {
timeouts[worker.id] = setTimeout(errorMsg, 2000);
});
cluster.on(‘listening‘, function(worker, address) {
clearTimeout(timeouts[worker.id]);
});
cluster.on(‘exit‘, function(worker, code, signal) {
clearTimeout(timeouts[worker.id]);
errorMsg();
});
Event: ‘online‘
worker
Worker
object
分支出一个新的工作进程后,工作进程会响应一个在线消息。当主进程收到一个在线消息后,它会触发该事件。‘fork‘ 和 ‘online‘ 的区别在于前者发生于主进程尝试分支出工作进程时,而后者发生于工作进程被执行时。
cluster.on(‘online‘, function(worker) {
console.log("Yay, the worker responded after it was forked");
});
Event: ‘listening‘
worker
Worker
objectaddress
Object
当工作进程调用 listen()
时,一个 listening
事件会被自动分配到服务器实例中。当服务器处于监听时,一个消息会被发送到那个‘listening‘事件被分发的主进程。
事件处理器被执行时会带上两个参数。其中 worker
包含了工作进程对象,address
对象包含了下列连接属性:地址 address
、端口号 port
和地址类型 addressType
。如果工作进程监听多个地址,那么这些信息将十分有用。
cluster.on(‘listening‘, function(worker, address) {
console.log("一个工作进程刚刚了连接到" + address.address + ":" + address.port);
});
地址类型addressType
为一下之一:
4
(TCPv4)6
(TCPv6)-1
(unix 域套接字)"udp4"
或者"udp6"
(UDP
v4 或者 v6)
Event: ‘disconnect‘
worker
Worker
object
当一个工作进程的 IPC 通道断开时此事件会发生。这发生于工作进程正常退出时,通常是调用 .kill()
或者被手动的断开连接之后。
当调用 .disconnect()
后,disconnect
和 exit
事件之间可能存在延迟。该事件可被用于检测进程是否被卡在清理过程或存在长连接。
cluster.on(‘disconnect‘, function(worker) {
console.log(‘工作进程 #‘ + worker.id + ‘ 断开了连接‘);
});
Event: ‘exit‘
worker
{Worker object}code
{Number} 如果是正常退出则为退出代码。signal
{String} 使得进程被终止的信号的名称(比如‘SIGHUP‘
)。
当任意工作进程被结束时,集群模块会分发exit
事件。
通过再次调用fork()
函数,可以使用这个事件来重启工作进程。
cluster.on(‘exit‘, function(worker, code, signal) {
console.log(‘worker %d died (%s). restarting...‘,
worker.process.pid, signal || code);
cluster.fork();
});
参看child_process
event: ‘exit‘.
Event: ‘setup‘
在.setupMaster()
被调用的第一时间发送该事件。
cluster.setupMaster([settings])
settings
{Object}exec
{String} 工作进程文件的路径。(默认为
)process.argv[1]
args
{Array} 传给工作进程的字符串参数。(默认为process.argv.slice(2)
)silent
{Boolean} 是否将输出发送到父进程的 stdio。(默认为false
)
setupMaster
被用于更改默认的 fork
行为。一旦被调用,设置的内容将被存放于cluster.settings
。
注意:
- 当且仅当首次调用
.setupMaster()
有效, 后续调用将被忽略。 - 由于以上原因,一个工作进程的唯一属性可以被每个工作进程传递给.fork()的env环境变量参数所设置。
.fork()
内部调用.setupMaster()
来创建默认值,
副作用就是若要调用方法.setupMaster()就必须在
.fork()之前。
示例:
var cluster = require("cluster");
cluster.setupMaster({
exec : "worker.js",
args : ["--use", "https"],
silent : true
});
cluster.fork();
这个函数只能在主进程中被调用。
cluster.fork([env])
env
{Object} 添加到子进程环境变量中的键值对。- 返回 {Worker object}
派生一个新的工作进程。这个函数只能在主进程中被调用。
cluster.disconnect([callback])
当callback
{Function}被调用时,所有工作进程都将断开连接并且处理将被关闭。
每个在 cluster.workers
的工作进程调用 .disconnect()
。
当工作进程都断开连接时,所有的内部的处理都会被关闭,使得主进程可以在没有其它事件等待时优雅地结束。
该方法带有一个可选的回调参数,会在完成时被调用。
这个函数只能在主进程中被调用。
cluster.worker
- Object
对当前工作进程对象的引用。在主进程中不可用。
var cluster = require(‘cluster‘);
if (cluster.isMaster) {
console.log(‘我是主进程‘);
cluster.fork();
cluster.fork();
} else if (cluster.isWorker) {
console.log(‘我是工作进程 #‘ + cluster.worker.id);
}
cluster.workers
- Object
一个储存活动工作进程对象的哈希表,以 id
字段作为主键。它能被用作遍历所有工作进程,仅在主进程中可用。
工作进程仅在 ‘disconnect‘
或者 ‘exit‘
事件发送之前在cluster.workers
中被移除。
// 浏览所有工作进程
function eachWorker(callback) {
for (var id in cluster.workers) {
callback(cluster.workers[id]);
}
}
eachWorker(function(worker) {
worker.send(‘向所有工作进程发送公告‘);
});
如果您希望通过通讯通道引用一个工作进程,那么使用工作进程的唯一标识是找到那个工作进程的最简单的办法。
socket.on(‘data‘, function(id) {
var worker = cluster.workers[id];
});
类: Worker
一个 Worker 对象包含了工作进程的所有公开信息和方法。可通过主进程中的 cluster.workers
或工作进程中的cluster.worker
取得。
worker.id
- String
每个新的工作进程都被赋予一个唯一的标识,这个标识被储存在 id
中。
当一个工作进程可用时,这就是它被索引在
cluster.workers 中的主键。
worker.process
- ChildProcess object
所有工作进程都是使用 child_process.fork()
创建的,该函数返回的对象被储存在
.process 属性中。在工作进程中则被存储在全局的process 变量中。
注意:只有 process
发生 ‘disconnect‘
事件且 .suicide
属性不为true,则工作进程调用 process.exit(0)
来防止意外断开连接。
worker.suicide
- Boolean
在通过 .kill()
或者 .disconnect()
设置之前值为 undefined
。
布尔值 worker.suicide
可以让你区分自主退出与意外退出,主进程将依据该值来决定是否重新派生一个工作进程。
cluster.on(‘exit‘, function(worker, code, signal) {
if (worker.suicide === true) {
console.log(‘Oh, it was just suicide\‘ – no need to worry‘).
}
});
// kill worker
worker.kill();
worker.send(message, [sendHandle])
message
ObjectsendHandle
Handle
object
该函数等同于 child_process.fork()
提供的
send 方法。在主进程中您可以用该函数向特定工作进程发送消息。
在工作进程中您也能使用 process.send(message)
,因为它们是同一个函数。
这个示例会回应来自主进程的所有消息:
if (cluster.isMaster) {
var worker = cluster.fork();
worker.send(‘hi there‘);
} else if (cluster.isWorker) {
process.on(‘message‘, function(msg) {
process.send(msg);
});
}
worker.kill([signal=‘SIGTERM‘])
signal
{String} 发送给工作进程的终止信号的名称
这个函数将完结工作进程。在主进程下,通过断开 worker.process
的连接且仅通过 signal
来kill。在工作进程下,
通过断开通信通道且通过 0
来退出。
引起.suicide
的设置。
该方法的别名是 worker.destroy()
,以保持向后兼容。
注意:在工作进程下, process.kill()
存在,这不是本函数而是 process.kill(pid,
[signal])。
worker.disconnect()
在工作进程下,该函数会关闭所有服务,并等待这些服务‘close‘事件的响应,然后断开IPC连接。
在主进程下,一个工作进程调用 .disconnect()
将产生一条发送给其本身的内部消息。
引起.suicide
的设置。
注意:调用该函数后工作进程将不再接受新连接,但新连接仍会被其它正在监听的工作进程处理。已存在的连接允许正常退出。当没有连接存在,连接到工作进程的 IPC 通道会被关闭,以便工作进程安全地结束。
以上仅要求服务连接和可会短连接不是被工作进程自主停止而且退出前disconnect ()不是在等待服务关闭。
注意:在工作进程下, process.disconnect
存在,但它不是本函数,而是child.disconnect()。
由于可能存在长生命周期服务断开连接阻塞其他工作进程,通常会发送一条消息告知应用来采取特定的动作来关闭服务。另一种做法是实现一个若一定时间内 disconnect
事件未发送就完结工作进程的超时机制。
if (cluster.isMaster) {
var worker = cluster.fork();
var timeout;
worker.on(‘listening‘, function(address) {
worker.send(‘shutdown‘);
worker.disconnect();
timeout = setTimeout(function() {
worker.kill();
}, 2000);
});
worker.on(‘disconnect‘, function() {
clearTimeout(timeout);
});
} else if (cluster.isWorker) {
var net = require(‘net‘);
var server = net.createServer(function(socket) {
// connections never end
});
server.listen(8000);
process.on(‘message‘, function(msg) {
if(msg === ‘shutdown‘) {
// initiate graceful close of any connections to server
}
});
}
Event: ‘message‘
message
Object
该事件和 child_process.fork()
所提供的一样。
工作进程中您也可以使用process.on(‘message‘)
。
举个例子,这里有一个集群,使用消息系统在主进程中统计请求的数量:
var cluster = require(‘cluster‘);
var http = require(‘http‘);
if (cluster.isMaster) {
// Keep track of http requests
var numReqs = 0;
setInterval(function() {
console.log("numReqs =", numReqs);
}, 1000);
// Count requestes
function messageHandler(msg) {
if (msg.cmd && msg.cmd == ‘notifyRequest‘) {
numReqs += 1;
}
}
// Start workers and listen for messages containing notifyRequest
var numCPUs = require(‘os‘).cpus().length;
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].on(‘message‘, messageHandler);
});
} else {
// Worker processes have a http server.
http.Server(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
// notify master about the request
process.send({ cmd: ‘notifyRequest‘ });
}).listen(8000);
}
Event: ‘online‘
和 cluster.on(‘online‘)
事件一样,但仅当特定工作进程的状态改变时发生。
cluster.fork().on(‘online‘, function() {
// Worker is online
});
在当前工作进程内不发送该事件。
Event: ‘listening
address
Object
和 cluster.on(‘listening‘)
事件一样,但仅当特定工作进程的状态改变时发生。
cluster.fork().on(‘listening‘, function(address) {
// Worker is listening
});
在当前工作进程内不发送该事件。
Event: ‘disconnect‘
和 cluster.on(‘disconnect‘)
事件一样,但仅当特定工作进程的状态改变时发生。
cluster.fork().on(‘disconnect‘, function() {
// Worker has disconnected
});
Event: ‘exit‘
code
{Number} 如果是正常退出则为退出代码。signal
{String} 使得进程被终止的信号的名称(比如‘SIGHUP‘
)。
由单个工作进程实例在底层子进程被结束时触发。详见子进程事件:
‘exit‘。
var worker = cluster.fork();
worker.on(‘exit‘, function(code, signal) {
if( signal ) {
console.log("worker was killed by signal: "+signal);
} else if( code !== 0 ) {
console.log("worker exited with error code: "+code);
} else {
console.log("worker success!");
}
});
Event: ‘error‘
这个事件与 child_process.fork()
提供的事件相同。
在工作进程中也可以使用 process.on(‘error‘)
。