众所周知,Node.js中的JavaScript代码执行在单线程中,非常脆弱,一旦出现了未捕获的异常,那么整个应用就会崩溃。这在许多场景下,尤其是web应用中,是无法忍受的。通常的解决方案,便是使用Node.js中自带的cluster模块,以master-worker模式启动多个应用实例。然而大家在享受cluster模块带来的福祉的同时,不少人也开始好奇:
让我们从Node.js项目的lib/cluster.js中的代码里,来一勘究竟。
问题一
为了得到这个问题的解答,我们先从worker进程的初始化看起,master进程在fork工作进程时,会为其附上环境变量NODE_UNIQUE_ID,是一个从零开始的递增数:
// lib/cluster.js// ...function createWorkerProcess(id, env) { // ... workerEnv.NODE_UNIQUE_ID = '' + id; // ... return fork(cluster.settings.exec, cluster.settings.args, { env: workerEnv, silent: cluster.settings.silent, execArgv: execArgv, gid: cluster.settings.gid, uid: cluster.settings.uid });}
随后Node.js在初始化时,会根据该环境变量,来判断该进程是否为cluster模块fork出的工作进程,若是,则执行workerInit()函数来初始化环境,否则执行masterInit()函数。
在workerInit()函数中,定义了cluster._getServer方法,这个方法在任何net.Server实例的listen方法中,会被调用:
// lib/net.js// ...function listen(self, address, port, addressType, backlog, fd, exclusive) { exclusive = !!exclusive; if (!cluster) cluster = require('cluster'); if (cluster.isMaster || exclusive) { self._listen2(address, port, addressType, backlog, fd); return; } cluster._getServer(self, { address: address, port: port, addressType: addressType, fd: fd, flags: 0 }, cb); function cb(err, handle) { // ... self._handle = handle; self._listen2(address, port, addressType, backlog, fd); }}
你可能已经猜到,问题一的答案,就在这个cluster._getServer函数的代码中。它主要干了两件事:
对于第一件事,由于master在接收,传递请求给worker时,会符合一定的负载均衡规则(在非Windows平台下默认为轮询),这些逻辑被封装在RoundRobinHandle类中。故,初始化内部TCP服务器等操作也在此处:
// lib/cluster.js// ...function RoundRobinHandle(key, address, port, addressType, backlog, fd) { // ... this.handles = []; this.handle = null; this.server = net.createServer(assert.fail); if (fd >= 0) this.server.listen({ fd: fd }); else if (port >= 0) this.server.listen(port, address); else this.server.listen(address); // UNIX socket path. /// ...}
新闻热点
疑难解答
图片精选