原書作者:樸靈 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()
的函式會在事件迴圈的當前迭代中(當前操作結束之後)被執行。 這意味著它會始終在setTimeout
和setImmediate
之前執行。 - 同步和非同步的區別。也就是說,是否是同步還是非同步,關注的是任務完成時訊息通知的方式。由呼叫方盲目主動問詢的方式是同步呼叫,由被呼叫方主動通知呼叫方任務已完成的方式是非同步呼叫;
- 是否是阻塞還是非阻塞,關注的是介面呼叫(發出請求)後等待資料返回時的狀態。被掛起無法執行其他操作的則是阻塞型的,可以被立即「抽離」去完成其他「任務」的則是非阻塞型的;
參考文章: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提供的net
、dgram
、http
、tls
等模組,讓面向網路程式設計更加便捷。
透過http
模組即可快速搭建Web伺服器;網路是輕IO操作,再配合上Nodejs非同步IO,Nodejs在面向網路程式設計方面能維持的併發量和QPS都是不容小覷的;
8.構建Web應用
告訴開發者如何透過Nodejs構建一個合格的網路應用服務。
- 使用Nodejs配合http模組搭建路由服務;
- 解析、使用和儲存Cookie;
- Session使用和儲存,包括如何高效管理Session;
- 透過網路快取避免頻寬浪費;
- 資料上傳需要注意點:大檔案使用流式解析、限制上傳內容的大小、避免CSRF攻擊加強校驗;
- 中介軟體的理念和實現;
9.玩轉程式
- 服務模型:同步——>複製程式——>多執行緒——>事件驅動;
child_process
模組搭建多程式;
感謝 樸靈 寫出這樣的好書,並且分享給開發者。