深刻淺出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
相關文章
相關標籤/搜索