問答方式學 Node.js

Q: 好了,我們繼續談談 Node.js 吧,我記得上次說到了「非阻塞」和「事件驅動」,這引起了我的好奇心,但是又給我潑了一桶冷水,這兩個詞有點高端。

A:別急,我們先來看一個簡單的場景:

人人網刷朋友新鮮事你應該用過吧?實現這樣的功能有一種簡單的方式,是讓用戶與服務器之間保持一個長輪詢。

但是它與普通的 Ajax 不一樣,服務器不會立馬返回信息,它會先 hold 住,等待應該返回信息了,它纔會返回信息(比如你的好友發了一條新的狀態)。

從傳統服務端來看(比如 Apache),每次一個新用戶連到你的網站上,你的服務器得新開一個連接,每個連接都需要佔用一個線程,這些線程大部分都是閒着的(比如等你的好友髮狀態,查數據庫等),雖然它們是閒着的,但是照樣佔用了內存,也就是說,如果用戶達到一定的規模,服務器的內存就會耗光而癱瘓。

解決辦法有很多,比如說使用線程池,但是它依然是阻塞的,如果線程池裏的所有線程都被阻塞(網速慢,被人惡意暫用)那麼接下來的請求將會排隊等待。

Node.js 就不相同了,它使用了「非阻塞」與「事件驅動」模型,你可以把它想象成一個 Event Loop 循環,這個循環會一直跑。一個新的請求來了,Event Loop 接收這個請求,然後交給其他線程,比如查詢數據庫,然後響應一個 callback,接着接收其他請求,而不是等待數據庫結果的返回。

如果數據庫返回了結果,服務端將會把它返回給客戶端,並繼續循環。這就是事件驅動:服務端只在有事情發生時,纔會有相應的處理(或者是接受請求,或者是一些 callback)。

Q:這麼看來,Node.js 的非阻塞和事件驅動,是基於這個 Event Loop 的?

A:是的,簡單來講,Node.js 的 Event Loop 是基於 libuv,而瀏覽器的 Event Loop 則是在 html5 規範 中定義,具體實現交給瀏覽器廠商。

Q:有趣了,還有兩種 Event Loop。

A:對比來看,它們有點相似:

在瀏覽器中比較簡單,值得注意的一點是,會在每個 tasks 之後,會把當前 microtask 隊列裏的任務都執行完:

Node.js 稍微複雜一點,每次 Event Loop 都需要經過六個階段,每一個階段之後,都會執行 nextTick、microtasks (resolved promise, 等):

   ┌───────────────────────┐
┌─>│        timers         │ <─── setTimeout/setInterval callback
│  └──────────┬────────────┘      ┌─────────────────────────┐
│             │                   │ nextTick queue          │
│             │ <───────────────  │                         │
│             │                   │ microTask queue         │
│  ┌──────────┴────────────┐      └─────────────────────────┘
│  │     I/O callbacks     │
│  └──────────┬────────────┘      ┌─────────────────────────┐
│             │                   │ nextTick queue          │
│             │ <───────────────  │                         │
│             │                   │ microTask queue         │
│  ┌──────────┴────────────┐      └─────────────────────────┘
│  │     idle, prepare     │ <─── 僅內部使用
│  └──────────┬────────────┘
│             │                   ┌─────────────────────────┐
│             │                   │ nextTick queue          │
│             │ <──────────────── │                         │
│             │                   │ microTask queue         │
│             │                   └─────────────────────────┘
│             │                   ┌─────────────────────────┐
│  ┌──────────┴────────────┐      │      incoming:          │
│  │         poll          │ <────┤      connections,       │
│  └──────────┬────────────┘      │      data, etc          │
│             │                   └─────────────────────────┘
│             │                   ┌─────────────────────────┐
│             │                   │ nextTick queue          │
│             │ <──────────────── │                         │
│             │                   │ microTask queue         │
│             │                   └─────────────────────────┘
│  ┌──────────┴────────────┐
│  │        check          │ <─── setImmediate callback
│  └──────────┬────────────┘      ┌─────────────────────────┐
│             │                   │ nextTick queue          │
│             │ <───────────────  │                         │
│             │                   │ microTask queue         │
│  ┌──────────┴────────────┐      └─────────────────────────┘
│  │    close callbacks    │ <─── eg: socket.on("close",func)
│  └──────────┬────────────┘      ┌─────────────────────────┐
│             │                   │ nextTick queue          │
│             │ <───────────────  │                         │
└─────────────┴                   │ microTask queue         │
                                  └─────────────────────────┘

來一段簡單的代碼,猜猜瀏覽器(Chrome)和 Node.js 分別輸出什麼:

console.log('start');

setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
      console.log('promise1');
  });
}, 0);

setTimeout(() => {
  console.log('timer2');
  Promise.resolve().then(() => {
    console.log('promise2');
  });
}, 0);

console.log('end');

Q:瀏覽器 (Chrome) 中肯定是輸出 start、end、time1、promise1、time2、promise2,至於 Node.js 中,我猜也是一樣的吧?

A:我們先來驗證一下:

瀏覽器中:

start
end
timer1
promise1
timer2
promise2

Node.js 中:

start
end
timer1
timer2
promise1
promise2

看來和想象中的不一樣,別急,看個動圖就會明白了:

瀏覽器中:

Node.js 中:

Q:原來是這樣,那如果在每個 setTimeout callback 里加上 process.nextTick 那麼是比 Promise.then 先執行?

A:是的,還記得上面所說過的嗎,在每個階段後都會執行 nextTick queue 以及 micktasks queue,nextTick queue 的優先級比 micktasks queue 高。

Q:我懂了。對了,我記得你提到了 libuv,它是什麼?不是說 Node.js 使用的是 v8 嗎,它和 v8 又有什麼關係?

A:...

未完待續...

其他

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章