Node.js運行原理、高併發性能測試對比及生態圈彙總

Node.js是從純前端走向更高階層的前端,以及全棧工程師的惟一快速途徑

  • 簡單的說Node.js 就是運行在服務端的 JavaScript
  • Node.js 是一個基於Chrome JavaScript 運行時創建的一個平臺
  • Node.js是一個事件驅動I/O服務端JavaScript環境,基於GoogleV8引擎,V8引擎執行Javascript的速度很是快,性能很是好

若是你是一個前端程序員,你不懂得像PHPPythonRuby等動態編程語言,而後你想建立本身的服務,那麼Node.js是一個很是好的選擇

  • Node.js 是運行在服務端的 JavaScript,若是你熟悉Javascript,那麼你將會很容易的學會Node.js
  • 固然,若是你是後端程序員,想部署一些高性能的服務,那麼學習Node.js也是一個很是好的選擇

Node.JS適合運用在高併發、I/O密集、少許業務邏輯的場景

Node.js的模塊組成以下:

Node.js的運行機制

  • V8引擎解析JavaScript腳本
  • 解析後的代碼,調用Node API
  • libuv庫負責Node API的執行。它將不一樣的任務分配給不一樣的線程,造成一個EventLoop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
  • V8引擎再將結果返回給用戶。

事件循環(Event Loop)

  • Nodejs 執行以後會初始化一個事件循環,執行代碼程序(這些程序可能會形成異步調用、定時器或者process.nextTick()),而後開始執行事件循環。
  • 事件循環的執行循序:

  • 上邊的每個模塊都是事件循環的一個階段,每一個階段都有一個要執行的回調的FIFO隊列。雖然每一個階段都不一樣,通常來講,當事件執行到一個階段,先執行這個階段特有的操做,而後操做這個階段的隊列,當隊列執行完或者達到了回調上限,事件循環就會執行下一個階段。

各個階段執行的任務以下:php

  • timers 階段: 這個階段執行setTimeoutsetInterval預約的callback;
  • I/O callbacks 階段: 執行除了close事件的callbacks、被timers設定的callbackssetImmediate()設定的callbacks這些以外的callbacks;
  • idle, prepare 階段: 僅node內部使用;
  • poll 階段: 獲取新的I/O事件, 適當的條件下node將阻塞在這裏;
  • check 階段: 執行setImmediate() 設定的callbacks;
  • close callbacks 階段: 執行socket.on('close', ...)這些 callback
  • process.nextTick()不屬於上面的任何一個phase,它在每一個phase結束的時候都會運行。也能夠認爲,nextTick在下一個異步方法的事件回調函數調用前執行。
TIPS: Node.js中的事件循環機制不會掉頭,只會由上往下,循環執行。

完整的一次執行機制能夠這樣描述

Node.js中,絕大部分 API都是異步的,有一個很形象的故事描述了 JAVA和Node.js的區別, JAVA是一個餐廳 100個服務員對應 100客戶, Node.js是一個服務員玩命幹,也對應 100個客戶,上菜的速度很大一部分取決於廚師的作菜速度

Node.js的單線程並非真正的單線程,只是開啓了單個線程進行業務處理(cpu的運算),同時開啓了其餘線程專門處理I/O。當一個指令到達主線程,主線程發現有I/O以後,直接把這個事件傳給I/O線程,不會等待I/O結束後,再去處理下面的業務,而是拿到一個狀態後當即往下走,這就是「單線程」、「異步I/O」。

  • I/O操做完以後呢?Node.jsI/O 處理完以後會有一個回調事件,這個事件會放在一個事件處理隊列裏頭,在進程啓動時node會建立一個相似於While(true)的循環,它的每一次輪詢都會去查看是否有事件須要處理,是否有事件關聯的回調函數須要處理,若是有就處理,而後加入下一個輪詢,若是沒有就退出進程,這就是所謂的「事件驅動」。這也從Node的角度解釋了什麼是」事件驅動」。
  • node.js中,事件主要來源於網絡請求,文件I/O等,根據事件的不一樣對觀察者進行了分類,有文件I/O觀察者,網絡I/O觀察者。事件驅動是一個典型的生產者/消費者模型,請求到達觀察者那裏,事件循環從觀察者進行消費,主線程就能夠快馬加鞭的只關注業務不用再去進行I/O等待。前端

    • 優勢: Node 公開宣稱的目標是 「旨在提供一種簡單的構建可伸縮網絡程序的方法」。咱們來看一個簡單的例子,在 Java和 PHP 這類語言中,每一個鏈接都會生成一個新線程,每一個新線程可能須要2MB的配套內存。在一個擁有8GBRAM的系統上,理論上最大的併發鏈接數量是4,000個用戶。隨着您的客戶羣的增加,若是但願您的Web應用程序支持更多用戶,那麼,您必須添加更多服務器。因此在傳統的後臺開發中,整個Web應用程序架構(包括流量、處理器速度和內存速度)中的瓶頸是:服務器可以處理的併發鏈接的最大數量。這個不一樣的架構承載的併發數量是不一致的。
    • 而Node的出現就是爲了解決這個問題:更改鏈接到服務器的方式。在Node 聲稱它不容許使用鎖,它不會直接阻塞 I/O 調用。Node在每一個鏈接發射一個在 Node 引擎的進程中運行的事件,而不是爲每一個鏈接生成一個新的 OS 線程(併爲其分配一些配套內存)。
  • 缺點:如上所述,nodejs的機制是單線程,這個線程裏面,有一個事件循環機制,處理全部的請求。在事件處理過程當中,它會智能地將一些涉及到IO、網絡通訊等耗時比較長的操做,交由worker-threads去執行,執行完了再回調,這就是所謂的異步IO非阻塞吧。可是,那些非IO操做,只用CPU計算的操做,它就本身扛了,好比算什麼斐波那契數列之類。它是單線程,這些本身扛的任務要一個接着一個地完成,前面那個沒完成,後面的只能乾等。所以,對CPU要求比較高的CPU密集型任務多的話,就有可能會形成號稱高性能,適合高併發的node.js服務器反應緩慢。

Node.js高併發使用Nginx+pm2,pm2中能夠開啓多線程負載均衡,模式分兩種:

pm2簡介: PM2node進程管理工具,能夠利用它來簡化不少 node應用管理的繁瑣任務,如性能監控、自動重啓、負載均衡等,並且使用很是簡單。

下面就對PM2進行入門性的介紹,基本涵蓋了PM2的經常使用的功能和配置。node

  • fork模式,單實例多進程,經常使用於多語言混編,好比php、python等,不支持端口複用,須要本身作應用的端口分配和負載均衡的子進程業務代碼。

缺點就是單服務器實例容易因爲異常會致使服務器實例崩潰。python

  • cluster模式,多實例多進程,可是隻支持node,端口能夠複用,不須要額外的端口配置,0代碼實現負載均衡。

優勢就是因爲多實例機制,能夠保證服務器的容錯性,就算出現異常也不會使多個服務器實例同時崩潰。webpack

  • 共同點,因爲都是多進程,都須要消息機制或數據持久化來實現數據共享。

pm2部署,默認開啓負載均衡:

  • npm i pm2 -g
  • $ pm2 start app.js # 啓動app.js應用程序
  • $ pm2 start app.js -i 4 # cluster mode 模式啓動4個app.js的應用實例 # 4個應用程序會自動進行負載均衡
  • pm2 start app.js -i max 根據你的cpu數量最大化啓動多線程進行負載均衡
  • 若是要中止全部應用,能夠pm2 stop all
  • 查看進程狀態 pm2 list
  • pm2真心很好很強大,能夠在線熱更新代碼,更多的指令須要上官網看

pm2Nginx配合

  • pm2 + nginx
  • 無非就是在nginx上作個反向代理配置,直接貼配置。
upstream my_nodejs_upstream {
 server 127.0.0.1:3001;
}
server { 
listen 80;
server_name my_nodejs_server;
root /home/www/project_root; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host; 
proxy_set_header X-NginX-Proxy true; 
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; 
proxy_set_header Connection "upgrade";
proxy_max_temp_file_size 0; proxy_pass http://my_nodejs_upstream/;
proxy_redirect off; proxy_read_timeout 240s;
    }
特別說明,咱們不建議使用 Node.js做爲底層服務器,更多時候做爲中間件和接入層使用,例如 Electron開發跨平臺應用
  • Nginx開啓多線程,負載均衡

負載均衡的做用

  • 負載均衡:分攤到多個操做單元上進行執行,和它的英文名稱很匹配。就是咱們須要一個調度者,保證全部後端服務器都將性能充分發揮,從而保持服務器集羣的總體性能最優,這就是負載均衡。

負載均衡這裏面涉及的東西相對也是比較多的,理論就不說太多了,網上,書上不少,今天咱們就利用Nginx服務器來實現一個簡單的負載均衡

負載均衡算法

  • 源地址哈希法:根據獲取客戶端的IP地址,經過哈希函數計算獲得一個數值,用該數值對服務器列表的大小進行取模運算,獲得的結果即是客服端要訪問服務器的序號。採用源地址哈希法進行負載均衡,同一IP地址的客戶端,當後端服務器列表不變時,它每次都會映射到同一臺後端服務器進行訪問。
  • 輪詢法:將請求按順序輪流地分配到後端服務器上,它均衡地對待後端的每一臺服務器,而不關心服務器實際的鏈接數和當前的系統負載。
  • 隨機法:經過系統的隨機算法,根據後端服務器的列表大小值來隨機選取其中的一臺服務器進行訪問。
  • 加權輪詢法:不一樣的後端服務器可能機器的配置和當前系統的負載並不相同,所以它們的抗壓能力也不相同。給配置高、負載低的機器配置更高的權重,讓其處理更多的請;而配置低、負載高的機器,給其分配較低的權重,下降其系統負載,加權輪詢能很好地處理這一問題,並將請求順序且按照權重分配到後端。
  • 加權隨機法:與加權輪詢法同樣,加權隨機法也根據後端機器的配置,系統的負載分配不一樣的權重。不一樣的是,它是按照權重隨機請求後端服務器,而非順序。
  • 最小鏈接數法:因爲後端服務器的配置不盡相同,對於請求的處理有快有慢,最小鏈接數法根據後端服務器當前的鏈接狀況,動態地選取其中當前積壓鏈接數最少的一臺服務器來處理當前的請求,儘量地提升後端服務的利用效率,將負責合理地分流到每一臺服務器。
  • 下載Nginx,找到config文件夾下面的nginx.conf,修改下面配置文件
  • 每一個
upstream test{ 
      server 11.22.333.11:6666 weight=1; 
      server 11.22.333.22:8888 down; 
      server 11.22.333.33:8888 backup;
      server 11.22.333.44:5555 weight=2; 
}
//down 表示單前的server臨時不參與負載.
//weight 默以爲1.weight越大,負載的權重就越大
//backup: 其餘所有的非backup機器down或者忙的時候,請求backup機器。因此這臺機器壓力會最輕

nginx命令彙總 :

nginx 服務器重啓命令,關閉
nginx -s reload :修改配置後從新加載生效
nginx -s reopen :從新打開日誌文件
nginx -t -c /path/to/nginx.conf 測試nginx配置文件是否正確

關閉nginx:
nginx -s stop :快速中止nginx
quit :完整有序的中止nginx

其餘的中止nginx 方式:

ps -ef | grep nginx

kill -QUIT 主進程號 :從容中止Nginx
kill -TERM 主進程號 :快速中止Nginx
pkill -9 nginx :強制中止Nginx

 

啓動nginx:
nginx -c /path/to/nginx.conf

平滑重啓nginx:
kill -HUP 主進程號

在開啓Nginx多線程負載均衡和部署pm2負載均衡後的架構圖:

  • 第一種,Node.js做爲底層服務器,直接操做數據庫的方式:

  • 第二種,Node.js做爲中間件,訪問底層服務器的方式:

高併發下性能對比,Apache、Nginx 與 Node.js 之爭

高併發下的性能測試對比:
  • 參考文章 : 巨頭終極對決,Apache、Nginx 與 Node.js 之爭
  • 全部的測試都在本地運行:
  • 英特爾酷睿 i7-2600k,四核八線程的機器
  • Gentoo Linux 是用於測試的操做系統
  • 用於基準測試的工具:ApacheBench,2.3 <$Revision: 1748469 $>
  • 測試包括一系列基準,從 1000 到 10000 個請求以及從 100 到 1000 個的併發請求——結果至關使人驚訝。

高併發測試結果對比:

  • Apache、Nginx 與 Node 的對比:請求負載的性能(每 100 位併發用戶)

  • Apache、Nginx 與 Node 的對比:用戶負載能力(每 1000 個請求)

  • 壓力測試

咱們能夠從結果中獲得什麼?
  • 從以上結果判斷,彷佛 Nginx 能夠在最少的時間內完成最多請求,換句話來講,Nginx 是最快的 HTTP 服務器。
  • 還有一個至關驚人的事實是,在特定的用戶併發數和請求數下,Node.js 能夠比 Nginx 和 Apache 更快。
  • 但當請求的數量在併發測試中增長的時候,Nginx 將重回領先的位置,這個結果可讓那些陷入 Node.js 的遐想的人清醒一下。
  • 和 Apache、Nginx 不一樣的是,Node.js 彷佛對用戶的併發數不太敏感,尤爲是在集羣節點。如圖所示,集羣節點在 0.1 秒左右保持一條直線,而 Apache 和 Nginx 都有大約 0.2 秒的波動。
  • 基於上述統計能夠得出的結論是:網站比較小,其使用的服務器就無所謂。然而,隨着網站的受衆愈來愈多,HTTP 服務器的影響變得越發明顯。
  • 當涉及到每臺服務器的原始速度的底線的時候,正如壓力測試所描述的,個人感受是,性能背後最關鍵的因素不是一些特定的算法,而其實是運行的每臺服務器所用的編程語言。
  • 因爲 Apache 和 Nginx 都使用了 C 語言—— AOT 語言(編譯型語言),而 Node.js 使用了 JavaScript ——這是一種 JIT 語言(解釋型語言)。這意味着 Node.js 在執行程序的過程當中還有額外的工做負擔。
  • 這意味着我不能僅僅基於上面的結果來下結論,而要作進一步校驗,正如你下面看到的結果,當我使用一臺通過優化的 Node.js 服務器與流行的 Express 框架時,我獲得幾乎相同的性能結論。
全面考慮
  • 逝者如斯夫,若是沒有服務的內容,HTTP 服務器是沒什麼用的。所以,在比較 we服務器的時候,咱們必須考慮的一個重要的部分就是咱們但願在上面運行的內容。
  • 雖然也有其它的功能,可是 HTTP 服務器最普遍的使用就是運行網站。所以,爲了看到每臺服務器的性能的實際效果,我決定比較一下世界上使用最普遍的 CMS(內容管理系統)WordPress 和 Ghost —— 內核使用了 JavaScript 的一顆冉冉升起的明星。
  • 基於 JavaScript 的 Ghost 網頁可否賽過運行在 PHP 和 Apache / Nginx 上面的 WordPress 頁面?
  • 這是一個有趣的問題,由於 Ghost 具備操做工具單一且一致的優勢——無需額外的封裝,而 WordPress 須要依賴 Apache / Nginx 和 PHP 之間的集成,這可能會致使顯著的性能缺陷。
  • 除此以外,PHP 距 Node.js 之間還有一個顯著的性能落差,後者更佳,我將在下面簡要介紹一下,可能會出現一些與初衷截然不同的結果。
PHP 與 Node.js 的對決
  • 爲了比較 WordPress 和 Ghost,咱們必須首先考慮一個影響到二者的基本組件。
  • 基本上,WordPress 是一個基於 PHP 的 CMS,而 Ghost 是基於 Node.js(JavaScript)的。與 PHP 不一樣,Node.js 有如下優勢:
  • 非阻塞的 I/O
  • 事件驅動
  • 更新穎、更少的殘舊代碼
  • 因爲有大量的測評文章解釋和演示了 Node.js 的原始速度超過 PHP(包括 PHP 7),我不會再進一步闡述這個主題,請你自行用谷歌搜索相關內容。
  • 所以,考慮到 Node.js 的性能優於 PHP,一個 Node.js 的網站的速度要比 Apache / Nginx 和 PHP 的網站快嗎?
  • WordPress 和 Ghost 對決
  • 當比較 WordPress 和 Ghost 時,有些人會說這就像比較蘋果和橘子,大多數狀況下我贊成這個觀點,由於 WordPress 是一個徹底成熟的 CMS,而 Ghost 基本上只是一個博客平臺。
  • 然而,二者仍然有共同競爭的市場,這二者均可以用於向世界發佈你的我的文章。
  • 制定一個前提,咱們怎麼比較兩個徹底基於不一樣的代碼來運行的平臺,包括風格主題和核心功能。
  • 事實上,一個科學的實驗測試條件是很難設計的。然而,在這個測試中我對更接近生活的情景更感興趣,因此 WordPress 和 Ghost 都將保留其主題。所以,這裏的目標是使兩個平臺的網頁大小盡量類似,讓 PHP 和 Node.js 在幕後鬥智鬥勇。
  • 因爲結果是根據不一樣的標準進行測量的,最重要的是尺度不同,所以在圖表中並排顯示它們是不公平的。所以,我改成使用表:
  • Node、Nginx、Apache 以及運行 WordPress 和 Ghost 的比較。前兩行是 WordPress,底部的兩行是 Ghost
  • Node、Nginx、Apache 以及運行 WordPress 和 Ghost 的比較。前兩行是 WordPress,底部的兩行是 Ghost
  • 正如你所見,儘管事實上 Ghost(Node.js)正在加載一個更小的頁面(你可能會驚訝 1kb 能夠產生這麼大的差別),它仍然比同時使用 Nginx 和 Apache 的 WordPress 要慢。
  • 此外,使用 Nginx 代理做爲負載均衡器來接管每一個 Node 服務器的請求實際上會提高仍是下降性能?
  • 那麼,根據上面的表格,若是說它產生什麼效果的話,它形成了更慢的效果——這是一個合理的結果,由於額外封裝一層理所固然會使其變得更慢。固然,上面的數字也代表這點差別能夠忽略不計。
  • 可是上表中最重要的一點是,即便 Node.js 比 PHP 快,HTTP 服務器的做用也可能超過某個 web 平臺使用的編程語言的重要性。
  • 固然,另外一方面,若是加載的頁面更多地依賴於服務器端的腳本處理,那麼我懷疑結果可能會有點不一樣。
  • 最後,若是一個 web 平臺真的想在這場競賽裏擊敗 WordPress,從這個比較中得出的結論就是,要想性能佔優,必需要定製一些像 PHP-FPM 的工具,它將直接與 JavaScript 通訊(而不是做爲服務器來運行),所以它能夠徹底發揮 JavaScript 的力量來達到更好的性能。

Node.js的生態圈彙總:

  • Node.js遵循commonJS規範,要說它的生態圈,第一個確定是webpack,用很差Node.js的人確定用很差webpack,因此說Node.js的一個突破初級前端工程師的好學習方向
  • express koa koa2 egg一系列的Node.js框架,在Restful架構下使用,完成常規的一些http,ajax請求響應
  • GraphQLGraphQL 是一種 API 所使用的查詢語言,不止Node.js有,其餘語言也有,不止能夠查詢,還能夠多數據庫CRUD操做,解決了一部分RestFul架構帶來的問題
  • mongodb,非關係型數據庫,輕量級別數據庫,目前Node.js配合使用的比較多的數據庫,在Node.js中咱們通常使用 mongoose這個庫來配合使用
  • sqlite,SQLite是一個進程內的庫,實現了自給自足的、無服務器的、零配置的、事務性的 SQL 數據庫引擎。它是一個零配置的數據庫,這意味着與其餘數據庫同樣,您不須要在系統中配置。就像其餘數據庫,SQLite 引擎不是一個獨立的進程,能夠按應用程序需求進行靜態或動態鏈接。SQLite 直接訪問其存儲文件。
  • Electron,跨平臺桌面開發,可使用Node.js的API,V8的環境也被打包在內。
  • C++插件,Node.js的V8環境就是C++寫的,天然也是可使用C++插件
  • Redis,數據緩存層,Redis支持主從同步。數據能夠從主服務器向任意數量的從服務器上同步,從服務器能夠是關聯其餘從服務器的主服務器。這使得Redis可執行單層樹複製。存盤能夠有意無心的對數據進行寫操做。因爲徹底實現了發佈/訂閱機制,使得從數據庫在任何地方同步樹時,可訂閱一個頻道並接收主服務器完整的消息發佈記錄。同步對讀取操做的可擴展性和數據冗餘頗有幫助。
  • SSR, 以React爲例,在中間層對代碼進行注水,在客戶端對代碼脫水,實現部分首屏SSR,優化首屏渲染時間。
  • websocket通信等
  • puppeteer爬蟲

總結一下Node.js

  • Node.js在目前前端的開發中,是一項不可或缺的技能,它也是讓咱們走向真正全棧工程師的路不那麼陡峭
  • Node.js適用場景,非密集型計算型
  • Node.js最核心的部分不止是RestFul架構的那一套接受請求,返回數據。還有文件IO,流,Buffer,redis層這一類的操做
  • Node.js配合Nginx進行負載均衡,不只能提高性能,更能替後端真正減輕不少負擔,完成許多特定的需求。
  • Node.js在作接入層,好比Electron中,能夠調用不少Node API,完成渲染進程不能作的事情,例如文件io,buffer操做等
今天因爲時間有限,不少東西都沒有細化下去寫,可能仍是有很多漏掉的,之後都會慢慢補上,走過路過,點點贊,我們永遠都是A

相關文章
相關標籤/搜索