简单的多进程服务
使用node.js写的服务器一贯很快,一部分是因为它的非阻塞I/O接口,还有是得益于优化的JavaScript V8引擎。当然,也有限制因素:JavaScript的单线程,所以只会用到处理器的一个内核。不过,通过 recluster (集群)模块(Github: doxout/recluster, MIT) 让多核处理器发挥出它的优势是有可能的。
###使用recluster
Recluster建立在Node.JS内部集群处理能力之上,为扩展子进程提供了一个简单的API,将程序扩展到所有可用的CPU核心。它也增加了一些细微的东西,像指数回避和热重启。
var cluster = recluster("/path/to/worker.js", {
workers: 4,
backoff: 10
});
cluster.run();
这样会产生4个工作进程,当一个进程停止会自动恢复。工作进程负责初始化应用,绑定sockets或接口。因为这些,通常使用Node.js的domain来正确封装你的应用,从而捕获异常:
var app = domain.create();
app.on("error", function(err) {
cluster.worker.disconnect();
});
app.run(function() {
http.createServer(function(req, res) {
res.end("Hello World\n");
}).listen(8000);
});
使用domain,任何异常都会被domain的错误管理器捕获,然后工作进程就可以优雅地死去了。当需要进程在一个不确定的状态下保持正常工作时,这通常是一个很好的选择。
你可能会注意到,服务是在worker进程中实现的,而非父进程,这意味着我们要在同一个端口上绑定很多次,这明显不行,但是Node的集群机制帮我们处理了这个问题:任何在Worker代码中绑定接口或socket的操作其实都是在它的父进程中实现的;所有的工作进程都会共享同样的socket.
这种对任何Node.js应用的简单处理方法,可以使其很容易地布署一个多进程服务器,但也有限制。
###状态和共享内存
Node.js操作的状态共享功能不是很多,而且没有本地的内存共享模型。有些项目需要Node.js提供一个共享的内存绑定,但在底层的灵性性上,可能会大打折扣。
当一个应用从一个进程扩展开来时,维护状态变得越来越没有价值。在理想的状态下,应用应该是完全无状态的。客户端不确定到底是哪一个进程处理了它的请求,所以这并不总是可行的,比如:维护那些像带有用户会话状态的连接,到合适的网络应用这种情况。这样,你会怎么办?
其实Node.js本身就开放了一个消息API,可以用来在进程间传递消息。对于非常简单的应用,这意味着所有的workers都会被通知到状态改变。
// Inside Worker
http.createServer(function(req, res) {
// do something to mutate state, and then...
process.send("State changed!");
}).listen(8000);
// Inside Parent
worker.on("message", function(message) {
// hypothetical connection pool object
pool.broadcast(message);
});
这可能在应用非常小的时侯工作地很好,但是在应用中它不能保证状态的统一,而且很容易引起状态的不连续。如果一个worker丢失了一条广播,或者在发送消息前它被kill掉了,这样我们就会有一个不一致的状态,而且非常难以找到原因,甚至变得更难调试了。
这不是说进程内消息没什么用,但当应用扩展变得复杂时它就会略显不足。特别是当我们从一个单进程程序,扩展到多服务器多集群模式时,消息传递就解决不到任何问题了。
更好的选择是创建一个共享的状态代理程序。通常这个后台程序用来替换掉内存状态,我们需要一个足够快的选项:像Redis那样的。(在Node.js中启用Redis不在本文讨论的范围,但它是一个相当简单的工作)。通过这样,我们就可以有效地将“状态”从应用层转移到持久层,还能得到更好地分层这个额外的奖励。
当你的应用把状态保持的需求处理好以后,我们就可以自信地发布我们的应用集群了,然后享用弹性,可扩展的,高性能的构架。
From:https://medium.com/on-coding/scaling-node-js-with-recluster-f04dd346108c