淺讀-《深入淺出Nodejs》

herryLo發表於2023-03-30

原書作者:樸靈 https://book.douban.com/subject/25768396/

這次算是重讀 深入淺出Nodejs,瞭解到很多之前忽略的細節,收穫蠻多,這次順便將其記錄分享,對學習和了解Nodejs有及其大的幫助。

1.Nodejs

    • 事件驅動、非阻塞IO,一個開源和跨平臺的 JavaScript 執行時環境
    • 非同步I/O:每個呼叫之間無須等待之前的I/O呼叫結束;
    • 事件:輕量級、松耦合、只關注事務點;
    • Node擅長I/O密集型的應用場景;(適合面向網路,不適合慢IO,如讀磁碟)

2.模組

    • CommonJS的模組規範。Node中引入模組三步:路徑分析、檔案定位、編譯執行;
    • 不論是核心模組還是檔案模組,require()方法對相同模組的二次載入都一律採用快取優先的方式,這是第一優先順序的。不同之處在於核心模組的快取檢查先於檔案模組的快取檢查;

3.非同步IO

  • 單執行緒非同步程式設計,極大的利用資源,避免單執行緒阻塞,更好的利用CPU;
  • 完美的非同步I/O應該是應用程式發起非阻塞呼叫,無須透過遍歷或者事件喚醒等方式輪詢,可以直接處理下一個任務,只需在I/O完成後透過訊號或回撥將資料傳遞給應用程式即可;
  • 注意:Nodejs單執行緒僅僅只是JavaScript執行在單執行緒中。在Node中,無論是*nix還是Windows平臺,內 部完成I/O任務的另有執行緒池;

Node非同步I/O

  • 事件迴圈:Node便會建立一個類似於while(true)的迴圈,每執行一次迴圈體的過程我們稱為Tick。每個Tick的過程就是檢視是否有事件待處理,如果有,就取出事件及其相關的回撥函式。如果存在關聯的回撥函式,就執行它們。然後進入下個迴圈,如果不再有事件處理,就退出程式;
  • 觀察者:在每個Tick的過程中,如何判斷是否有事件需要處理呢?這裡必須要引入的概念是觀察者。每個事件迴圈中有一個或者多個觀察者,而判斷是否有事件要處理的過程,就是向這些觀察者詢問是否有要處理的事件;

事件迴圈是一個典型的生產者/消費者模型。非同步I/O、網路請求等則是事件的生產者,源源不斷為Node提供不同型別的事件,這些事件被傳遞到對應的觀察者那裡,事件迴圈則從觀察者那裡取出事件並處理;

非I/O非同步API

setTimeout()setInterval()setImmediate()process.nextTick()

setTimeout

  • setTimeout()setInterval()與瀏覽器中的API是一致的,分別用於單次和多次定時執行任務。
  • 呼叫setTimeout()或者setInterval()建立的定時器會被插入到定時器觀察者內部的一個紅黑樹中。每次Tick執行時,會從該紅黑樹中迭代取出定時器物件,檢查是否超過定時時間,如果超過,就形成一個事件,它的回撥函式將立即執行。

process.nextTick

  • 每次呼叫process.nextTick()方法,只會將回撥函式放入佇列中,在下一輪Tick時取出執行;

setImmediate

  • setImmediate() 引數傳入的任何函式都是在事件迴圈的下一個迭代中執行的回撥;
  • 延遲 0 毫秒的 setTimeout() 回撥與 setImmediate() 非常相似。 執行順序取決於各種因素,但是它們都會在事件迴圈的下一個迭代中執行;

區別

  • 傳給 process.nextTick() 的函式會在事件迴圈的當前迭代中(當前操作結束之後)被執行。 這意味著它會始終在 setTimeoutsetImmediate 之前執行。
  • 同步和非同步的區別。也就是說,是否是同步還是非同步,關注的是任務完成時訊息通知的方式。由呼叫方盲目主動問詢的方式是同步呼叫,由被呼叫方主動通知呼叫方任務已完成的方式是非同步呼叫;
  • 是否是阻塞還是非阻塞,關注的是介面呼叫(發出請求)後等待資料返回時的狀態。被掛起無法執行其他操作的則是阻塞型的,可以被立即「抽離」去完成其他「任務」的則是非阻塞型的;

參考文章:https://zhuanlan.zhihu.com/p/22707398

4.非同步程式設計

  • 優點:利用事件迴圈的方式,JavaScript執行緒像一個分配任務和處理結果的大管家,I/O執行緒池裡的各個I/O執行緒都是小二,負責兢兢業業地完成分配來的任務,小二與管家之間互不依賴,保持整體高效率;
  • 缺點:這個模型的缺點則在於管家無法承擔過多的細節性任務,如果承擔太多,則會影響到任務的排程;(CPU密集型是弱點)

非同步程式設計解決方案

  • 事件釋出/訂閱模式;
  • Promise/Deferred模式;
  • 流程控制庫;

非同步併發控制

  • 非同步呼叫的併發限制在不同場景下的需求不同:非實時場景下,讓超出限制的併發暫時等待執行可以滿足需求;(一個佇列來控制併發量,如果當前活躍(指呼叫發起但未執行回撥)的非同步呼叫量小於限定值,從佇列中取出執行。如果活躍呼叫達到限定值,呼叫暫時存放在佇列中。❑ 每個非同步呼叫結束時,從佇列中取出新的非同步呼叫執行)

5.記憶體控制

  • V8堆記憶體的最大值在64位系統上為1464 MB, 32位系統上則為732 MB;
  • 在V8中,主要將記憶體分為新生代和老生代兩代。新生代中的物件為存活時間較短的物件,老生代中的物件為存活時間較長或常駐記憶體的物件;
  • Node的記憶體構成主要由透過V8進行分配的部分和Node自行分配的部分,受V8的垃圾回收限制的主要是V8的堆記憶體。(利用堆外記憶體可以突破記憶體限制的問題,如 Buffer
  • 記憶體洩漏原因:快取、佇列消費不及時、作用域未釋放;
  • 操作大檔案可以使用stream模組用於處理;

6.理解Buffer

  • Buffer主要用於操作位元組;
  • 小而頻繁的Buffer操作時,採用slab的機制進行預先申請和事後分配,使得JavaScript到作業系統之間不必有過多的記憶體申請方面的系統呼叫;
  • 大塊的Buffer物件,直接使用C++層面提供的記憶體;

7.網路程式設計

Nodejs提供的netdgramhttptls等模組,讓面向網路程式設計更加便捷。

透過http模組即可快速搭建Web伺服器;網路是輕IO操作,再配合上Nodejs非同步IO,Nodejs在面向網路程式設計方面能維持的併發量和QPS都是不容小覷的;

8.構建Web應用

告訴開發者如何透過Nodejs構建一個合格的網路應用服務。

  1. 使用Nodejs配合http模組搭建路由服務;
  2. 解析、使用和儲存Cookie;
  3. Session使用和儲存,包括如何高效管理Session;
  4. 透過網路快取避免頻寬浪費;
  5. 資料上傳需要注意點:大檔案使用流式解析、限制上傳內容的大小、避免CSRF攻擊加強校驗;
  6. 中介軟體的理念和實現;

9.玩轉程式

  1. 服務模型:同步——>複製程式——>多執行緒——>事件驅動;
  2. child_process模組搭建多程式;

感謝 樸靈 寫出這樣的好書,並且分享給開發者。

相關文章