深入浅出Node.js学习笔记(九)

玩转进程

JavaScript运行在单个进程的单个线程上。它带来的好处是:程序状态是单一的,在没有多线程的情况下没有锁、线程同步问题,操作系统在调度时也因为较小上下文的切换。可以很好地提高CPU的使用率。

如何充分利用多核CPU服务器?

如何保证进程的健壮性和稳定性?

1. 服务模型的变迁

从“古”至今,Web服务器的架构以及经历了几次的变迁。服务器处理客户端请求的并发量,就是每个里程碑的见证。

1.1 石器时代:同步

最早的服务器,其执行模型是同步的,它的服务模式是一次只为一个请求服务,所有请求都得按次序等待服务。

1.2 青铜时代:复制进程

为了解决同步架构的并发问题,一个简单的改进是通过进程的复制同时服务更多的请求和用户。

为了解决启动缓慢的问题,预复制(prefork)被引入服务模型中,即预先复制一定数量的进程。

1.3 白银时代: 多线程

为了解决进程复制中的浪费问题,多线程被引入服务模型,让一个线程服务一个请求。

线程相对进程的开销要小许多,并且线程之间可以共享数据,内存浪费的问题可以得到解决,并且利用线程池可以减少创建和销毁线程的开销。

但是多线程所面临的并发问题只能说比多进程略好,因为每个线程都拥有自己独立的堆栈,这个堆栈都需要占用一定的内存空间。

另外。由于一个CPU核心在一个时刻只能做一件事情,操作系统只能通过CPU切分为时间片的方法,让线程可以较为均匀地使用CPU资源,但是操作系统内核在切换线程的同时也要切换线程的上下文,当线程数量过多时,时间将会被耗用在上下文切换中。

1.4 黄金时代:事件驱动

为了解决高并发问题,基于事件驱动的服务模型出现了,像Node与Nginx均是基于事件驱动的方式实现的,采用单线程避免了不必要的内存开销和上下文切换开销。

由于所有处理都在单线程上进行,影响事件驱动服务模型性能的点在于CPU的计算能力,它的上限决定这类服务模型的性能上限,但它不受多进程或多进程模式中资源上限的影响,可伸缩性远比前两者高。

2. 多进程架构

Master-Worker模式,又称为主从模式,其中进程分为两种:主进程和工作进程。

这是典型的分布式架构中用于并行处理业务的模式,具备较好的可伸缩性和稳定性。

主进程不负责具体的业务处理,而是负责调度或管理工作进程,趋于稳定。

工作进程负责具体的业务处理,因为业务的多种多样,甚至一项业务由多人开发完成,所以进程的稳定性值得开发者关注。

2.1创建子进程

child_process模块给予Node可以随意创建子进程(child_process)的能力。

它提供了4个方法用于创建子进程:

  • spawn():启动一个子进程来执行命令;
  • exec():启动一个子进程来执行命令,与spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况;
  • execFile():启动一个子进程来执行可执行文件;
  • fork():与spawn()类似,不同点在于它创建的子进程只需指定执行的JavaScript文件模块即可;

2.2 进程间通信

在Master-worker模式中,要实现主进程管理和调度工作进程的功能,需要主进程和工作进程之间的通信。对于child_process模块,创建好了子进程,然后与父子进程间通信是十分容易的。

在前端浏览器中,JavaScript与主进程与UI渲染共用一个线程。执行JavaScript的时候UI渲染是停滞的,渲染UI时,JavaScript是停滞的,两者相互阻塞。长时间执行JavaScript将会造成UI停顿不响应。

为了解决这个问题HTML5提出了WebWorker API。WebWorker允许创建工作线程并在后台运行,使得一些较为严重的计算不影响主线程上的UI渲染。

主线程与工作线程之前通过onmessage()和postMessage()进行通信,子进程对象由send()方法实现主进程向子进程发送数据,message事件实现收听子进程发来的数据,与API在一定程度上相似。

通过fork()或者其他API,创建子进程之后,为了实现父子进程之间的通信,父进程与子进程之间会创建IPC通道。通过IPC通道,父子进程之间才能通过message()和send()传递消息。

  • 进程间通信原理

    IPC的全称是Inter-Process Communication,即进程间通信。进程间通信的目的是为了让不同的进程能够相互访问资源并进行协调工作。

    实现进程间的技术有很多,如命名管道、匿名管道、socket、信号量、共享内存、消息队列、Domain Socket等。

    Node中实现IPC通道的是管道(pipe)技术。

    IPC通道是用命名管道或Domain Socket创建的,它们与网络socket的行为比较类似,属于双向通信。

2.3 句柄传递

通过代理,可以避免端口不能重复监听的问题,甚至可以在代理进程上做适当的负载均衡,使得每个子进程可以较为均衡地执行任务。

由于进程每接收到一个连接,将会用掉一个文件描述符,因此代理方案中客户端连接到代理进程,代理进程连接到工作进程的过程需要用掉两个文件描述符。

操作系统的文件描述符是有限的,代理方案浪费掉一倍数量的文件描述符的做法影响了系统的扩展能力。为了解决这样的问题,Node在版本0.5.9引入了进程间发送句柄的功能。

send()方法除了能通过IPC发送数据外,还能发送句柄,带二个可选参数就是句柄。

child.send(message,[sendHandle])

句柄:是一个可以用来标识资源的引用,它的内部包含了指向对象的文件描述符。

句柄可以用来标识一个服务器socket对象、一个客户端socket对象。一个UDP套接字、一个人管道等。

3. 集群稳定之路

搭建好了集群,充分利用了多核CPU资源,似乎就可以迎接客户端大量的请求。但需要考虑的细节:

  • 性能问题
  • 多个工作进程的存活状态管理
  • 工作进程的平滑重启
  • 配置或者静态数据的动态重新载入
  • 其他细节

3.1 进程事件

Node的进程事件

  • error
  • exit
  • close
  • disconnect

3.2 自动重启

一旦有未捕获的异常出现,工作进程就会立即停止接收新的连接。当所有连接断开后,退出进程。主进程在侦听到工作进程的exit后,将会立即启动新的进程服务,以保证整个集群总是有进程为用户服务的。

  1. 自杀信号

    自杀(suicide)信号:工作进程早得知要退出时,向主进程发送一个自杀信号,然后才停止接收新的连接,当所有连接断开后才退出。主进程在接收到自杀信号后,立即创建新的工作进程服务。

  2. 限量重启

    为了消除这种无意义的重启,在满足一定规划的限制下,不应当反复重启。

3.3 负载均衡

在多进程之间监听相同的端口,使得用户请求能够分散到多个进程上进行处理,可以将CPU资源都调用起来。保证多个处理工作单元量公平的策略叫负载均衡。

Node默认提供的机制是采用操作系统的强占式策略,所谓强占式策略就是在一堆进程中,闲着的进程对到来的请求进行争抢,谁抢到谁服务。

一般而言,这种抢占式策略是公平的,各个进程可以根据自己的繁忙度来进行抢占。但对于Node而言,需要分清它的繁忙是由CPU、I/O两个部分构成的,影响抢占的是CPU的繁忙度。对于不同的业务,可能存在I/O繁忙,而CPU较为空闲的情况,这可能造成某个进程能够抢到较多请求,形成负载不均衡的情况。

Node提供的轮叫调度(Round-Robin):由主进程接收连接,将其依次分发给工作进程。

Round-Robin可以避免CPU和I/O繁忙差异导致的负载不均衡。Round-Robin策略也可以通过代理服务器来实现,但是它会导致服务器上消耗的文件描述符是平常方式的两倍。

3.4 状态共享

在不允许共享数据的情况下,来实现多个进程之前的共享。

  1. 第三方数据存储

    将数据存放到数据库、磁盘文件、缓存服务(如Redis)中,所有工作进程启动时将其读取进内存中。

    缺点:如果数据发生改变,还需要一种机制通知到各个子进程,使得它们的内部状态也得到更新。

    状态同步的机制;

    • 各个子进程去向第三方进行定时轮询
    • 当数据发生更新时,主动通知子进程
  2. 主动通知

    当数据发生更新时,主动通知子进程。

    通知进程:用来发送通知和查询状态是否更改的进程。

4. Cluster模块

Cluster模块:用来解决多核CPU的利用率问题,同时也提供较完整的API,用以处理进程的健壮性问题。

4.1 Clister工作原理

Cluster模块就是child_process和net模块的组合应用。

4.2 Cluster事件

Cluster事件:

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

推荐阅读更多精彩内容

  • 前言 通过前边的学习,大家应该已经充分理解了node的单线程只不过是js层面的单线程,是基于V8引擎的单线程,因为...
    白昔月阅读 4,545评论 3 13
  • JavaScript运行在单个进程的单个线程上,它带来的好处是:程序的状态是单一的,在没有多线程的情况下没有锁、线...
    Upcccz阅读 939评论 0 1
  • 0. 背景 单线程运行模型Node.js架构在Chrome V8引擎之上,它的模型与浏览器类似,js代码运行在单个...
    十年一剑_阅读 2,800评论 0 0
  • 转载自http://blog.csdn.net/qq295445028/article/details/79930...
    WebSSO阅读 2,903评论 0 3
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 3,525评论 0 5