深入浅出 Node.js Cluster

本文首发于猫眼前端团队公众号 https://mp.weixin.qq.com/s/Xm_c841UdKA06s76rJ_4nw

前言

如果大家用 PM2 管理 Node.js 进程,会发现它支持一种 cluster mode。开启 cluster mode 后,支持给 Node.js 创建多个进程。 如果将 cluster mode 下的 instances 设置为 max 的话,它还会根据服务器的 CPU 核心数,来创建对应数量的 Node 进程。


PM2 其实利用的是 Node.js Cluster 模块来实现的,这个模块的出现就是为了解决 Node.js 实例单线程运行,无法利用多核 CPU 的优势而出现的。那么,Cluster 内部又是如何工作的呢?多个进程间是如何通信的?多个进程是如何监听同一个端口的?Node.js 是如何将请求分发到各个进程上的?如果你对上述问题还不清楚,不妨接着往下看。

核心原理

Node.js worker 进程由child_process.fork()方法创建,这也意味存在着父进程和多个子进程。代码大致是这样:

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  for (var i = 0, n = os.cpus().length; i < n; i += 1) {
    cluster.fork();
  }
} else {
   // 启动程序 
}

学过操作系统的同学,应该对 fork() 这个系统调用不陌生,调用它的进程为父进程,fork 出来的都是子进程。子进程和父进程具有相同的数据空间、堆栈,但是它们的内存空间不共享。父进程(即 master 进程)负责监听端口,接收到新的请求后将其分发给下面的 worker 进程。这里涉及三个问题:父子进程通信、负载均衡策略以及多进程的端口监听。

进程通信

master 进程通过 process.fork() 创建子进程,他们之间通过 IPC (内部进程通信)通道实现通信。操作系统的进程间通信方式主要有以下几种:

  • 共享内存
    不同进程共享同一段内存空间。通常还需要引入信号量机制,来实现同步与互斥。
  • 消息传递
    这种模式下,进程间通过发送、接收消息来实现信息的同步。
  • 信号量
    信号量简单说就是系统赋予进程的一个状态值,未得到控制权的进程会在特定地方被强迫停下来,等待可以继续进行的信号到来。如果信号量只有 0 或者 1 两个值的话,又被称作“互斥锁”。这个机制也被广泛用于各种编程模式中。
  • 管道
    管道本身也是一个进程,它用于连接两个进程,将一个进程的输出作为另一个进程的输入。可以用 pipe 系统调用来创建管道。我们经常用的“ | ”命令行就是利用了管道机制。

Node.js 为父子进程的通信提供了事件机制来传递消息。下面的例子实现了父进程将 TCP server 对象句柄传给子进程。

const subprocess = require('child_process').fork('subprocess.js');

// 开启 server 对象,并发送该句柄。
const server = require('net').createServer();
server.on('connection', (socket) => {
  socket.end('被父进程处理');
});
server.listen(1337, () => {
  subprocess.send('server', server);
});
process.on('message', (m, server) => {
  if (m === 'server') {
    server.on('connection', (socket) => {
      socket.end('被子进程处理');
    });
  }
});

那么问题又来了,如果进程间没有父子关系,换句话说,我们应该如何实现任意进程间的通信呢?大家可以去看看这篇文章:进程间通信的另类实现

负载均衡策略

前面提到,所有请求是通过 master 进程分配的,要保证服务器负载比较均衡的分配到各个 worker 进程上,这就涉及到负载均衡策略了。Node.js 默认采用的策略是 round-robin 时间片轮转法。

round-robin

round-robin 是一种很常见的负载均衡算法,Nginx 上也采用了它作为负载均衡策略之一。它的原理很简单,每一次把来自用户的请求轮流分配给各个进程,从 1 开始,直到 N(worker 进程个数),然后重新开始循环。这个算法的问题在于,它是假定各个进程或者说各个服务器的处理性能是一样的,但是如果请求处理间隔较长,就容易导致出现负载不均衡。因此我们通常在 Nginx 上采用另一种算法:WRR,加权轮转法。通过给各个服务器分配一定的权重,每次选出权重最大的,给其权重减 1,直到权重全部为 0 后,按照此时生成的序列轮询。

可以通过设置 NODE_CLUSTER_SCHED_POLICY 环境变量,或者通过 cluster.setupMaster(options) 来修改负载均衡策略。读到这里大家可以发现,我们可以 Nginx 做多机器集群上的负载均衡,然后用 Node.js Cluster 来实现单机多进程上的负载均衡。

多进程的端口监听

最初的 Node.js 上,多个进程监听同一个端口,它们相互竞争新 accept 过来的连接。这样会导致各个进程的负载很不均衡,于是后来使用了上文提到的 round-robin 策略。具体思路是,master 进程创建 socket,绑定地址并进行监听。该 socket 的 fd 不传递到各个 worker 进程。当 master 进程获取到新的连接时,再决定将 accept 到的客户端连接分发给指定的 worker 处理。简单说就是,master 进程监听端口,然后将连接通过某种分发策略(比如 round-robin),转发给 worker 进程。这样由于只有 master 进程接收客户端连接,就解决了竞争导致的负载不均衡的问题。但是这样设计就要求 master 进程的稳定性足够好了。

总结

本文以 PM2 的 Cluster Mode 作为切入点,向大家介绍了 Node.js Cluster 实现多进程的核心原理。重点讲了进程通信、负载均衡以及多进程端口监听三个方面。通过研究 cluster 模块可以发现,很多底层原理或者是算法,其实都是通用的。比如 round-robin 算法,它在操作系统底层的进程调度中也有使用;比如 master-worker 这种架构,是不是在 Nginx 的多进程架构中也似曾相识;比如信号量、管道这些机制,也可以在各种编程模式中见到它们的身影。当下市面上各种新技术层出不穷,但核心其实是万变不离其宗,理解了这些最基础的知识,剩下的也可以触类旁通了。


参考链接:

  1. 当我们谈论 cluster 时我们在谈论什么(下)
  2. Node.js进阶:cluster模块深入剖析
  3. 进程间通信的另类实现
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281