[Node.js] 06 - Multi-thread and process module

課前閱讀:關於Node.js後端架構的一點後知後覺javascript

書推薦:<Node.js design patterns>html

衍生問題: 微服務的必要性,Flux架構java

 

 

容錯性和拓展性


1、立體拓展

假設如今須要你用NodeJS搭建一個http服務,我猜想你會藉助express框架用不到10行的代碼完成這項工做。node

容錯性拓展性纔是正常運行的基本保障,至少保證了你的服務是可用的,永遠是可用的。
算法

    • X軸方向:純粹的對服務實例進行拓展,例如爲了響應更多的請求  
    • y軸方向:爲服務添加新的功能,功能性拓展
    • z軸方向:按照業務數據對服務進行拓展(這裏沒搞懂,不知道這麼說是否準確)

 

增長服務實例也分爲兩類,橫向拓展(horizontal scaling)和縱向拓展(vertical scaling),數據庫

    • 橫向,表示利用更多的機器,      // ----> (B)
    • 縱向,表示在同一臺機器上挖掘它的潛力。// ----> (A)

 

 

2、服務實例拓展

  • (A) 縱向拓展

單進程express

NodeJS程序是以單進程形式運行,32位機器上最多也只有1GB內存的實用權限(在64位機器上最大的內存權限擴大到1.7GB)。編程

而目前絕大部分線上服務器的CPU都是多核而且至少16GB起,如此以來Node程序便沒法充分發揮機器的潛力。json

多進程c#

同時NodeJS本身也意識到了這一點,因此它容許程序建立多個子進程用於運行多個實例。

具體技術細節涉及到Cluster模塊,詳情能夠查看NodeJS相關文檔: https://nodejs.org/api/cluster.html

    • 主進程master

master主進程並不實際的處理業務邏輯,但除了業務邏輯之外事情它都作:它是manager,

負責啓動子進程,

管理子進程(若是子進程掛了要及時重啓),

它也扮演router,也就是對該程序的訪問請求首先到達主進程,再由主進程分配請求給子進程worker。

    • 子進程

子進程才負責處理業務邏輯。

 

在這個機制下有兩條細節須要咱們定奪如何處理。

    • 負載均衡的難點

如何把外界的請求平均的分配給不一樣的worker處理?這裏的平均不是指數量上的平均(由於單條請求處理的工做量可能不一樣),而是既不能讓某個子進程太閒,也不能讓某個子進程太忙,保證它們始終處於工做的狀態便可。這也是咱們常說的負載均衡(load-balancing)

默認狀況下Cluster模塊採用的是 round robin 負載均衡算法,說白了就是依次按順序把請求派給列表上的子進程,派到結尾以後又重頭開始。

這個算法只能保證每一個子進程收到的請求個數是平均的,和隨機算法相似。但若是某個子進程遇到問題,處理變得遲緩了,然後續的請求又源源不斷的分配過來,那麼這個子進程的壓力就大了,這就略顯不公了。除此以外咱們還要考慮到超時,重作等機制的創建。因此主進程master做爲路由時不只僅是轉發請求,還要能智能的分配請求。

    • 狀態共享的難點

另外一個問題是狀態共享問題,假如某個用戶第一次訪問該服務時是分配給了線程A上的實例A處理,而且用戶在這個實例上進行了登錄,而沒有過幾秒鐘以後當用戶第二次訪問時分配給了線程B上的實例B處理,若是此時用戶在A上的登錄狀態沒有共享給其餘實例的話,那麼用戶不得不從新登錄一次,這樣的用戶體驗是沒法接受的。

 

  • (B) 橫向拓展

主進程-子進程的模式思路不只適用於縱向拓展,還適用於橫向拓展。

當單臺機器已經沒法知足你需求的時候,你能夠把單實例子進程的概念拓展爲單臺機器:咱們將在多臺機器上部署多個進行實例,用戶的訪問請求也並不是直接到達它們,而是先到達前方的代理機器,它也是負責負載均衡的機器,負責將請求轉發給部署了應用實例的機器。

這樣的模式咱們也一般稱爲反向代理模式

 

 

3、功能拓展

既然咱們沒法保證功能不會出錯,那咱們有沒有辦法保證當一個功能出錯以後不會影響整個程序的正常運行?這也是咱們所說的容錯性

道理都懂,咱們都明白程序須要容錯,因此try/catch是從編碼上解決這個問題。

咱們容許程序出錯,可是要及時把錯誤隔離,而且再也不影響程序的運行。這個就要從架構上解決這個問題。例如使用微服務(Microservices)架構

 

  • 爲何要用微服務
    • 單體(monolithic)架構介紹

在介紹微服務架構以前,咱們要了解其它架構爲何無法知足咱們的要求。例如咱們經常使用的單體(monolithic)架構。單體架構這個詞你可能不熟悉,但幾乎咱們天天都在和它打交道,大部分的後端服務都歸屬於單體架構,對它的解釋我翻譯Martin Fowler的描述:

企業級應用一般分爲三個部分:

  1. 用戶界面(包含運行在用戶瀏覽器上的html頁面和javascript腳本),
  2. 數據庫(一般是包含許多表的關係數據庫),
  3. 服務端應用。

服務端應用將會處理http請求,執行業務邏輯,從數據庫中取得數據,生成html視圖返回給瀏覽器。

這樣的服務端應用就被稱爲單體(monolith)——單個具備邏輯性的執行過程。任何針對系統的修改都會致使從新構建和部署一個新版本的服務端應用。

(注:以上這段描述摘自Martin Fowler的文章Microservices,我認爲這是對微架構描述最全面的文章,若是想對這一小節作更深刻的瞭解能夠把這篇文章細讀。 這也是我讀到的Martin Fowler所寫的文章中最通俗的文章。我的認爲Martin Fowler的文章讀起來比較晦澀,John Resig緊隨其後)

    • 單體(monolithic)架構de問題

單體架構是一種很天然的搭建應用的方式,它符合咱們對業務處理流程的認知。但單體應用也存在問題:

任何一處,不管大小的修改都會致使整個應用被從新構建和從新部署。隨着應用規模和複雜性的不斷增大,參與維護的人數增多,每一輪迭代修改的模塊增多,對上線來講是極大的考驗,對於內部單個模塊的拓展也是極爲不利的。例如當圖片壓縮請求劇增時,須要新增圖片壓縮模塊的實例,但實際上不得不擴展整個單體應用的實例。

    • 微服務是方案

微服務架構解決的就是這一系列問題。顧名思義,微服務架構下軟件是由多個獨立的服務組成。這些服務相互獨立互不干預。以拆分上面所說的單體應用爲例,咱們能夠把處理HTTP請求的模塊和負責數據庫讀寫的模塊分離出來成爲獨立的服務,這兩個模塊從功能上看是沒有任何交集。這樣的好處就是,咱們能夠獨立的部署,拓展,修改這些服務。例如應用須要添加新的接口時,咱們只須要修改處理HTTP請求的服務,只公開這部分代碼給修改者,只上線這部分服務,拓展時也只須要新添這部分服務的實例。

微服務和咱們一般編寫的模塊(以文件爲單位,以命名空間爲單位)相比更加獨立,更像是一個五臟俱全的「小應用」,若是你讀完了我以前推薦的Martin Fowler關於微服務的文章的話,你會對這點更深有感觸:微服務除了在運維上獨立之外,它還能夠擁有獨立的數據庫,還應該配備獨立的團隊維護。它甚至能夠容許使用其餘的語言進行開發,只要對外接口正常便可。

    • 微服務de不足

固然微服務也存在不足,例如

    1. 如何將諸多的微服務在大型架構中組織起來,
    2. 如何提升不一樣服務之間的通訊效率都是須要在實際工做中解決的問題。

 

微服務說到底仍是解耦思想的實踐。從這個意義上來講,React下的Flux架構某種意義上也屬於微服務。

若是你瞭解Flux的起源的話,Flux架構 其實來源於後端的CQRS,即Command Query Responsibility Segregation,命令與查詢職責分離,也就是將數據的讀操做和寫操做分離開。

這麼設計的理由有不少,舉例說一點:在許多業務場景中,數據的讀和寫的次數是不平衡,可能上千次的讀操做纔對應一次寫操做,好比機票餘票信息的查詢和更新。因此把讀和寫操做分開可以有針對性的分別優化它們。例如提升程序的scalability,scalability意味着咱們可以在部署程序時,給讀操做和寫操做部署不一樣數量的線上實例來知足實際的需求。

 

  • 類比unity設計模式 - 自治

若是你也有Unity編程經驗的話會對解耦更有感觸,在Unity中咱們已經不能稱之爲解耦,而是自治,這是Unity的設計模式

舉個例子,屏幕上少則可能有十幾個遊戲元素,例如玩家、敵人還有子彈。你必須爲它們編寫「死亡」的規則,「誕生」的規則,交互的規則。由於你根本沒法預料玩家在什麼時候何種位置發射出子彈,也沒法預料子彈什麼時候在什麼位置碰撞上什麼狀態敵人。

因此你只能讓它們在規則下自由發揮。這和微服務有殊途同歸之妙:獨立,隔離,自治。

 

 

4、Node.js 與 Netty Server

  • 良心對比一

Jeff: netty可能有更好的封裝,來解決例如「狀態共享」這樣的難題。

protobuf比Json更好一點:http://www.infoq.com/cn/articles/json-is-5-times-faster-than-protobuf

  • 良心對比二

From: https://my.oschina.net/lifeofpi/blog/120210

如下代碼體現了nodejs的簡潔性。

var http = require('http');
http.createServer(
function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, "127.0.0.1");
console.log(
'Server running at http://127.0.0.1:1337/');

 

 

 

多進程/多線程實踐


1、JS 角度的多線程

Goto: [JS] ECMAScript 6 - Async : compare with c#

  • Javascript異步編程的4種方法 - Ruan

(1) 回調函數

(2) 事件監聽:func.trigger --> func.on

(3) 發佈訂閱:jQuery.publish --> jQuery.subscribe

(4) Promises對象

 

2、Promise 對象

有三種狀態,也就支持了.then這樣的鏈式寫法。

Ref: 淺談ES6的Promise對象【看上去講得更好】

 

  • 使用 Bluebird 在生產環境

原生promise可能不穩定,爲了防止不可預知的bug,在生產項目中最好仍是不要使用原生的或者本身編寫的Promise(目前爲止並非全部瀏覽器都能很好的兼容ES6),而是使用已經較爲成熟的有大量小夥伴使用的第三方Promise庫,下面就爲小夥伴推薦一個—— Bluebird

 

  • catch 比 then() 的 rejected 回調好些
promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
)

 

/* 須要根據連接進一步學習 */

 

 

3、Generator 函數

狀態機、遍歷器 ----> yield & next 函數 

 


4、Async 函數

ES2017 標準引入了 async 函數,是個Generator 函數的語法糖。

 

 

5、node.js角度的多線程

Ref: Node.js的process模塊

Ref: Node.js 多進程

Ref: nodejs 多核處理模塊cluster【基於以上兩篇的技術,居然有了負載均衡】

cluster是一個nodejs內置的模塊,用於nodejs多核處理。cluster模塊,能夠幫助咱們簡化多進程並行化程序的開發難度,輕鬆構建一個用於負載均衡的集羣。

 

/* implement */

相關文章
相關標籤/搜索