首页 > 语言 > JavaScript > 正文

详解通过源码解析Node.js中cluster模块的主要功能实现

2024-05-06 15:34:11
字体:
来源:转载
供稿:网友

众所周知,Node.js中的JavaScript代码执行在单线程中,非常脆弱,一旦出现了未捕获的异常,那么整个应用就会崩溃。这在许多场景下,尤其是web应用中,是无法忍受的。通常的解决方案,便是使用Node.js中自带的cluster模块,以master-worker模式启动多个应用实例。然而大家在享受cluster模块带来的福祉的同时,不少人也开始好奇:

    为什么我的应用代码中明明有app.listen(port);,但cluter模块在多次fork这份代码时,却没有报端口已被占用? Master是如何将接收的请求传递至worker中进行处理然后响应的?

让我们从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,若master进程是第一次接收到监听此端口/描述符下的worker,则起一个内部TCP服务器,来承担监听该端口/描述符的职责,随后在master中记录下该worker。 Hack掉worker进程中的net.Server实例的listen方法里监听端口/描述符的部分,使其不再承担该职责。

对于第一件事,由于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. /// ...}            
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选