nodejs
- Non-Blocking I/O Model
- Event Loop
- Event-Driven
- 基本架構
- 何為阻塞
- 程式碼執行時
- 阻止事件迴圈的幾個維度
- Worker Pool
- npm模組的風險
Non-Blocking I/O Model
non-blocking是指node.js程式中不同步等待執行非javascript操作
(例如I/O)完成而繼續執行下一塊程式碼的特性。
注:CPU密集型屬於javascript操作。
I/O通常指與磁碟
與網路
的互動
非阻塞I/O模型使得nodejs支援高併發且非常適合於I/O密集型應用
Nodejs Event Loop and Worker Pool
共6個階段
timers
setTimeout與setInterval回撥函式佇列pending callbacks
會在下一次loop中執行的系統級回撥佇列。如TCP ECONNREFUSED -idle,prepare
內部使用poll
接收新的I/O事件。執行I/O相關回撥。在這個階段node程式可能會阻塞check
setImmediate回撥會在這個階段執行close
一些關閉的回撥。比如connect.on('close', () => {....})
注:process.nextTick不屬於任何一個階段,它是介於任意兩個階段之間,並且在階段切換時執行nextTick回撥
Event-Driven
基本架構
- nodejs事件驅動架構中有兩種執行緒:事件迴圈執行緒(Event Loop)以及工作執行緒池(worker pool)
- Event Loop負責編排客戶端請求而後排程Worker Pool處理CPU密集型任務
注:因此nodejs並不是純粹的單執行緒語言!
何為阻塞
- 如果Event Loop執行回撥或worker執行任務需要很長時間,即為阻塞。當發生阻塞時,主要會有兩點需要考慮:
- 效能:如果某worker執行緒定期執行heavyweight任務,會影響服務吞吐量(請求/秒)
- 安全性:假設某些輸入會引起程式阻塞,則存在被惡意客戶端利用並攻擊的風險。即拒絕服務攻擊。
程式碼執行時
- 在Event Loop中
同步
執行常規的變數、方法的定義與呼叫,javascript所有回撥以及非阻塞非同步I/O如網路I/O - Worker Pool是libuv(執行緒池工作排程的c++庫)在Worker Pool中
非同步
執行“昂貴”繁重的任務。node提供非阻塞I/O(作業系統不提供)API,以及CPU密集的I/O API- I/O密集型API:
- DNS: dns.lookup()
- fs: fs.readFile(),除了那些顯示說明同步的方法
- CPU密集型API:
- crypto: crypto.pbkdf2()
- zlib: 除了那些顯示說明同步的方法
- I/O密集型API:
Event Loop實質
抽象來說,Event Loop維護掛起事件的佇列,Worker Pool維護掛起任務的佇列。
實際上,Event Loop並不是維護一個佇列。而是一個檔案描述符的集合
,這些檔案描述符從系統級事件通知機制獲取比如epoll(linux),kqueue(OSX),IOCP(Windows)。這些檔案描述符對應於某些網路套接字以及node正在監視的檔案等等。當某個描述符準備好時,Event Loop會將其轉換為合適的事件並執行對應的回撥。
另外,Worker Pool維護的是一個真正的佇列。Worker會pop出佇列的task並執行,完成後會觸發Event Loop“至少一個事件已完成”的事件。
阻止事件迴圈的幾個維度
- 資料處理流程中是否包含計算複雜度高的任務,比如使用CPU密集型Node API比如crypto,fs,zlib,child-process(分割槽處理與offload to Worker Pool)
- ReDoS攻擊,檢查是否存在易受攻擊的正規表示式(使用安全正規表示式庫做安全校驗)
- 是否在主執行緒中使用JSON.parse以及JSON.stringify(潛在風險,因此也建議offload給Worker Pool)
Worker Pool
nodejs預設的Worker Pool專門用於處理I/O任務,維護自己的執行緒池可以使用cluster模組以及child_process模組做自定義執行緒池。
Node伺服器的吞吐量取決於
WorkerPool的吞吐量。有效降低逐個任務時間開銷
以及穩定任務時間開銷的變化
將最大程度提升伺服器的吞吐量。最常見的方法就是複雜重複型任務(比如陣列迭代)做分割槽處理。
注:由於排程Worker Pool會增加額外的通訊開銷,因為Worker Pool無法獲取主執行緒的名稱空間從而無法直接讀取Javascript物件,所以需要序列化/反序列化導致增加通訊成本。
npm模組的風險
npm生態系統中存在數十萬個模組為開發者提供了極大的便利,然而社群中npm包良莠不齊,因為無法較為準確的估計其使用Event Loop或者Worker Pool的成本而導致一些程式隱患。