Node伺服器是如何處理大量請求的?

如果是同一時間海量的請求到達Node伺服器上,node是如何處理的?

感覺node沒有開啟其餘線程,還能處理大量請求,著實不解


看你怎麼定義「處理請求」了,在 2015 mbp i7 上面,單進程 Node.js 的性能:

  • hello world http server: 2w qps
  • Vue ssr:20~130 qps

可見,什麼叫做「大量請求」,完全看你的定義和請求的消耗。

如果說在 Node.js 剛出現的時候在處理「大量請求」上有一些優勢的話,就是兩點:

  1. 在腳本語言的運行環境中,v8 算是性能很好的,在性能上有一定優勢
  2. Node.js 的單進程單線程無阻塞IO + event loop 模式,能hold住大量的並發請求

尤其是後一點跟 php-fpm 形成鮮明對比。

Node.js 在等待非同步 IO 時主線程是空閑的,可以處理後續請求,因而可以處理大量的並發。

而 php-fpm 每個worker進程同一時間內只能處理一個請求,對待並發只能往上堆進程數。如果大量請求被IO阻塞住了, worker 進程短缺,後續請求就沒法處理了 ,從而造成 nginx 502 bad gateway 出錯。

在 Node.js 剛出現的時候,非同步IO+事件循環模型帶來的高並發特點,確實給人們帶來了一些新鮮感,有很多公司用 Node.js 來解決 c10k 問題。但在性能更高、非同步編程更方便的 go 語言出現之後,Node.js 的這一點也沒有特別突出了。

當你不知道 Node.js 在什麼情況下適合作為伺服器使用,你就記住:Node.js 適合 IO-bound 場景,不適合 CPU-bound 場景;適合 JS 程序員,不適合非 JS 程序員。


這個問題問到了本質。

可以看一下一個客戶端請求來的時候,伺服器做什麼:

client = accept(server) // 請求進來
parse(client) // 對請求解析,比如轉換為 HTTP 格式,花費 1ms
read(client) // 讀取客戶端的數據 花費 2ms
queryMysql(client) // 查詢自己的 Mysql 伺服器 花費 10ms
queryGraphServer(client) // 查詢自己的圖形演算法伺服器 花費 20ms
send(client, result) // 把結果返回客戶端 花費 1ms
close(client) // 關閉或者保持連接

如你所見,每個操作都是花費時間的,這次為客戶端服務花費的時間大約是 33 ms,其中大部分時間花費在 **查詢自己的 Mysql 伺服器** 和 **查詢自己的圖形演算法伺服器**。這兩個操作都是遠程調用,也就是你在自己的伺服器啟動一個客戶端連接,去請求這些自己的內部伺服器,得到結果,再綜合返回給外部的客戶端。

既然是遠程調用,那就涉及到了**等待**,當你去請求內部伺服器時,你所在的伺服器就在等待。這就是時間浪費!為此,Unix 系統經過多年的實踐和探索,推出了 select、poll 等技術來解決這些浪費。目前,Linux Epoll、BSDUnix KQueue、Solaris Event-Ports 都提供了更高效的解決辦法:event notify --- 事件通知。

當 queryMysql 時,可以把當時的通信 socket 設置為非阻塞 `fcntl(socket, NON_BLOCKING)`,並且在一個事件數據表存儲其相關的文件描述符和關聯存儲。當操作系統檢測到有 IO 數據到來時,會發出通知,並且在你獲取的 events 表中進行登記。那麼你就可以對此表中存在的文件描述符進行操作,他們是真正的正在 IO,你去 queryMysql、queryGraphServer 不會花費時間等待。

至於 Node.js,Go net,Java netty 等等不過是對此功能的高階封裝,並且各自提供自己的編程模式。


處理高流量的模型跟什麼語言沒關係,甚至跟計算機都沒關係。排隊論的數學模型,前輩們早就玩得很溜,網路設備的Qos控制,早就應用到生產系統上。Performance Modeling and Design of Computer Systems: Queueing Theory in Action (9781107027503): Mor Harchol-Balter: Books

處理並發,首先要確認,什麼樣的task,誰來執行task。那我們常見的web應用curd來說:

task的任務:對資料庫curd,做些簡單的業務判斷,格式重新包裝。

應用層(java,c#,node,php): 發起資料庫操作,做些簡單的業務判斷,格式重新包裝。

資料庫:執行sql。

很明顯一個task的執行時間,大部分都在資料庫上。無論你應用層,採用事件驅動,協程還是線程組。資料庫如mysql都是按一個連接一個線程的方式執行sql。

對於一個垂直分散式系統的整體吞吐量,適合木桶原則,短板決定整體的能力。

對於應用層來將,只要保證3點即可:1給資料庫餵飽,2合理分發任務,3自己不要被流量拖垮。

如果task的種類多樣化:有些是cpu密集型如圖片處理,有些是純應用層輕量計算或者返回緩存數據,有些是io密集型,請求大量第三方rpc。應用層就必須做好task的分散,必要時多開幾個渠道,避免task的塞車,或者把cpu密集型的分發到一個渠道。

另外所有應用都有內存限制,應用每接一個task,都要給出相應的內存,內存多了又要看內存的垃圾回收能不能匹配上。所以接不了那麼多客的話,就要及時關門貼個公共,或者挑一些不重要的task扔掉。

如果應用層提供的api需要區分權重,可能需要有套優先順序或者搶佔式的qos機制。

node這種非同步的編程框架:對比java serlet那種多線程的框架,比較明顯的優勢就是不用管線程池的配置。但有得必有失,沒有線程級別的stack,就沒有threadlocal,就會惹出一堆煩心事。


請求多了可以用cluster模塊開多進程處理呀。如果更多,就前面架設負載均衡,用多個伺服器處理。反正單個伺服器總有能力上限的,用什麼語言都會遇到。


因為其非同步編程的特點在處理io密集型的場景有很大的優勢。

真正需要高並發的伺服器恐怕還是用c++才能搞定吧。


Node js 線程是主線程。開線程的非同步操作,都被 libuv 幹了。你當然感覺不到。


推薦閱讀:

鬥魚是怎麼做到十幾萬人超清直播的,帶寬夠嗎?
docker和virtualenv有什麼區別?
在採購之前如何評估伺服器的性能夠不夠,主要是CPU?
IBM 和 Google 等公司組建的 OpenPower 聯盟,會有何影響?
蘋果要求App強制使用https 。如何把伺服器的 http 協議升級到https 協議?

TAG:伺服器 | Nodejs | 單線程 |