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

玩转进程

JavaScript运行在单个进程的单个线程上。它带来的好处是:程序状态是单一的,在没有多线程的状况下没有锁、线程同步问题,操做系统在调度时也由于较小上下文的切换。能够很好地提升CPU的使用率。前端

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

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

1. 服务模型的变迁

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

1.1 石器时代:同步

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

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
相关文章
相关标签/搜索