怎么理解Node.js,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
一、Node簡介
二、模塊機制
A.CommonJS規范
1.模塊引用:通過require()方法來引入外部模塊
2.模塊定義:提供exports對象用于導出當前模塊的方法或者變量,并且是唯一導出的出口
3.模塊標識:就是傳遞給require()方法的參數,必須是符合小駝峰命名的字符串,或者以.、..開頭的相對路徑
B.Node的模塊實現
1.不論是核心模塊還是文件模塊,require()方法對相同模塊的干凈加載都一律采用緩存優先的方式,這是第一優先級的
2.核心模塊》路徑形式的文件模塊》自定義模塊(自定義模塊的生成方式與JS原型鏈或作用域鏈的查找方式十分類似)
3.Node會按.js、.json、.node次序補足擴展名,在嘗試的過程中,需要調用fs模塊同步阻塞式地判斷文件是否存在,這里會是一個引起性能問題的地方,如果是.node和.json文件,在傳遞給require()時帶上擴展名
4.js模塊的編譯:包裝成(function(exports, require,module,__filename,__dirname)){….})的方式
C.核心模塊
1.JS核心模塊
Node采用了V8附帶的js2c.py工具,將所有內置的JS代碼轉換成C++里的數組,生成node_natives.h頭文件
與文件模塊的區別在于:獲取源代碼的方式(核心模塊是從內存中加載的)以及緩存執行結果的位置
2.C/C++核心模塊
C++主內完成核心,JS主外實現封裝的模式,Node的buffer、crypto、evals、fs、os等模塊都是部分通過C/C++編寫的
D.C/C++擴展模塊
1.JS的一個典型弱點是位運算,效率不高
E.模塊調用棧
1.C/C++內建模塊屬于最底層模塊,如果不是非常了解要調用的C/C++內建模塊,盡量避免使用process.binding()方法直接調用
2.JS核心模塊的職責:作為C/C++內建模塊的封裝層和橋接層;純粹的功能模塊;
3.文件模塊通常由第三方編寫,包括普通JS模塊和C/C++擴展模塊
F.包與NPM
1.包描述文件:package.json,可以幫助Node解決依賴包安裝的問題
G.前后端共用模塊
1.AMD、CMD規范
三、異步I/O
A.為什么要異步I/O
1.用戶體驗
2.資源分配
單線程同步編程模型會因阻塞I/O導致硬件資源得不到更優的使用。多線程編程模型也因為編程中的死鎖、狀態同步等問題讓開發人員頭疼
Node在兩者之間給出了它的方案:利用單線程,遠離多線程死鎖、狀態同步等問題;利用異步I/O,讓單線程遠離阻塞,以更好地使用CPU
B.異步I/O實現現狀
1.阻塞/非阻塞:操作系統內核對于I/O只有兩種方式,阻塞與非阻塞
在調用阻塞I/O時,應用程序需要等待I/O完成才返回結果
阻塞I/O的一個特點是調用之后一定要等到系統內核層面完成所有操作后,調用才結束
非阻塞I/O的差別是調用之后立即返回,返回的并 不是業務層期望的數據,而僅僅是當前調用的狀態。為了獲取完整的數據,需要重復調用I/O操作來確認是否完成
這種重復調用判斷操作是否完成的技術叫做輪詢:read(原始、性能最低)、select(改進read,只能同時檢查1024個文件描述符)、poll(采用鏈表方式,但文件描述符多的情況下性能還是十分低下)、epoll(目前Linux下效率最高的I/O事件通知機制,真實利用了事件通知、執行回調的方式,而不是遍歷查詢)、kqueue(僅在FreeBSD系統下存在)
2.理想的非阻塞異步I/O:AIO(僅支持Linux,僅支持內核I/O中的0_DIRECT方式讀取,無法利用系統緩存)
3.現實的異步I/O:模擬線程池、glibc的AIO、libeio、windows下的IOCP
C.Node的異步I/O
1.事件循環:Node自身的執行模型,在進程啟動時,Node便會創建一個類似于while(true)的循環,每執行一次循環體的過程我們稱為Tick,每個Tick的過程就是查看是否有事件待處理,如果有,就取出事件及其相關的回調函數。如果存在關聯的回調函數,就執行它們
2.觀察者:每個事件循環中有一個或者多個觀察者,而判斷是否有事件要處理的過程就是向這些觀察者詢問是否有要處理的事件,瀏覽器采用了類似的機制,Node中有文件I/O觀察者、網絡I/O觀察者等
3.事件循環是一個典型的生產者/消費者模型。異步I/O、網絡請求等則是事件的生產者,事件被傳遞到觀察者那里,事件循環則從觀察都那里取出事件并處理
4.請求對象:從JS發起調用到內核執行完I/O操作的過渡過程中,存在一種中間產物,叫做請求對象
5.事件循環、觀察者、請求對象、I/O線程池這四者共同構成了Node異步I/O模型的基本要素
D.非I/O的異步API
1.定時器
setTimeout()和setInterval()與瀏覽器中的API是一致的,他們的實現原理與異步I/O類似,只是不需要I/O線程池的參與
利用定時器觀察者內部的一個紅黑樹,定時器并不精確
2.process.nextTick()
相對輕量,每次調用時,只會將回調函數放入隊列中,在下一輪Tick時取出執行
定時器時間復雜度為O(lg(n)),nextTick()時間復雜度為O(1)
3.setImmediate()
與nextTick()類似,優先級比nextTick()低,原因在于事件循環對觀察者的檢查是有午后順序的,nextTick()屬于idle觀察者,setImmediate()屬于check觀察者
idle觀察者->I/O觀察者->check觀察者
E.事件驅動與高性能服務器
1.Node通過事件驅動 的方式處理請求,無須為每一個請求創建額外的對應線程,可以省掉創建線程和銷毀線程的開銷,同時操作系統在調度任務時因為線程少,上下文切換的代價很低
2.Nginx同樣采用事件驅動的方式
四、異步編程
A.函數式編程
1.高階函數:可以將函數作為參數或是返回值,并形成了一種后續傳遞風格,將函數的業務重點從返回值轉移到了回調函數中
2.偏函數:是指創建一個調用另外一個部分——參數或變量已經預置的函數——的函數用法。通過指定部分參數來產生一個新的定制函數的形式就是偏函數
B.異步編程的優勢與難點
1.優勢
Node帶來的最大特性莫過于事件驅動的非阻塞I/O模型,這是它的靈魂所在
Node是為了解決編程模型中阻塞I/O的性能問題的,采用了單線程模型,這導致Node更像一個處理I/O密集問題的能手
呆計算不影響異步I/O的調度,那就不構成問題,建議對CPU的耗用不要超過10ms,或者將大量的計算分解為諸多的小量計算,通過setImmediate()進行調度
2.難點
異步處理:Node在處理異常上形成了一種約定,將異步作為回調函數的第一個參數傳回,不要對用戶傳遞的回調函數進行異常捕獲
函數嵌套過程:對于Node而言,事務中多個異步調用的場景比比皆是,這并沒有利用好異步I/O帶來的并行優勢
阻塞代碼:沒有sleep()這樣的線程沉睡功能
多線程編程:由于前端瀏覽器存在對標準的滯后性,Web Workers沒有流行下來,Node借鑒了這個模式,child_process是其基礎API,cluster模塊是更深層次的應用
異步轉同步:偶爾出現的同步需求將會因為沒有同步API讓開發者突然無所適從
C.異步解決方案
1.事件發布/訂閱模式
事件監聽器模式是一種廣泛用于異步編程的模式,是回調函數的事件化,又稱發布/訂閱模式
Node自身提供的events模塊是發布/訂閱模式的一個簡單實現,Node中部分模塊都繼承自它
事件發布/訂閱模式自身并無同步和異步調用的問題,但在Node中,emit()調用多半是伴隨事件循環而異步觸發的,所以廣泛應用于異步編程
常常用來解耦業務邏輯,也是一種鉤子機制,利用鉤子導出內部數據或狀態給外部的調用者
如果對一個事件添加了超過10個偵聽器,會得到警告;為了處理異常,EventEmitter對象對error事件進行了特殊對待
利用once解決緩存雪崩問題
2.Promise/Deferred模式
Promises/A:只要具備then()有一份發即可
Promise通過封裝異步調用,實現了正向用例和反向用例的分享以及邏輯處理延遲
Promise模式比原始的事件偵聽和觸發略為優美,它的缺陷則是需要為不同的場景封裝不同的API,沒有直接的原生事件那么靈活
Promise和秘決其實在于對隊列的操作
3.流程控制庫
尾觸發與Next:除了事件和Promise外,還有一類方法是需要手工調用才能持續執行后續調用的,我就將此類方法叫做尾觸發,常見的關鍵詞是next,應用最多的是Connect的中間件
中間件機制使得在處理網絡請求時,可以像面向切面 編程一樣進行過濾、驗證、日志等功能,而不與具體業務邏輯產生關聯,以致產生耦合
中間件并不要求每個中間方法都是異步的,但是如果每個步驟都采用異步來完成,實際上只是串行化的處理,沒辦法通過并行的異步調用來提升業務的處理效率
async方法:series()實現一組任務的串行執行;parallel()實現并行異步操作;waterfall()實現前一個結果是后一個的輸入;auto實現自動依賴處理
Step庫:默認實現串行方式,this中包含parallel()方法實現并行,group()實現分組
Wind庫
D.異步并發控制
1.異步I/O與同步I/O的顯著差距:同步I/O因為每個I/O都是彼此阻塞的,在循環體中,總是一個接一個調用,不會出現耗用文件描述符太多的情況,同時性能也是低下的;對于異步I/O,雖然并發容易實現,但是由于太容易實現,依然需要控制。盡管是要壓榨底層系統的恒通,但還是需要給予一定的過載保護,以防止過猶不及
2.bagpipe的解決方案
通過一個隊列來控制并發量
如果當前活躍(指調用發起但未執行回調)的異步調用量小于限定值,從隊列中取出執行
如果活躍調用達到限定值,調用暫存放在隊列中
每個異步調用結束時,從隊列中取出新的異步調用執行
3.async的解決方案:parallelLimit()方法
五、內存控制
A.V8的垃圾回收機制與內存限制
1.V8的內存限制:64位系統下約為1.4GB,32位系統下約為0.7GB
2.V8中,所有的JS對象都是通過堆來進行分配的,使用process.memoryUsage()來查看,heapTotal和heapUsed表示已申請到的內存和當前使用的量,rss是resident set size的縮寫,即進程的常駐內存部分
3.在V8中,主要將內存分為新生代和老生代,新生代中的對象為存活時間較短的對象,老生代中的對象為存活時間較長或常駐內存的對象
4.在分代的基礎上,新生代中的對象主要通過Scavenge算法進行垃圾回收。在Scavenge的具體實現中,主要采用了Cheney算法;在老生代中主要采用了Mark-Sweep和Mark-Compact相結合的方式進行垃圾回收
5.為了降低全堆垃圾回收帶來的停頓時間,V8先從標記階段入手,將原本要一口氣停頓完成的動作改為增量標記(incremental marking),也就是拆分為這么多小“步進”,每做完一“步進”就讓JS應用邏輯執行一小會兒,垃圾回收與應用邏輯交替執行直到標記階段完成
B.高效使用內存
1.作用域:如果變量是全局變量(不通過var或定義在global變量上),由于全局作用域需要直到進程退出才能釋放,此時將導致引用 的對象常駐內存(常駐在老生代中),如果需要釋放常駐內存的對象,可以通過delete操作來刪除引用關系,在V8中通過delete刪除對象的屬性有可能干擾V8的優化,所以通過賦值方式解除引用更好
2.閉包:一旦有變量引用中間函數,這個中間函數將不會釋放,同時也支使原始的作用域不會得到釋放,作用域中產生的內存占用也不會得到釋放。除非不同有引用,都會逐步釋放
C.內存指標
1.查看內存使用情況
2.os模塊中的totalmem()和freemen()這兩個方法用于查看操作系統的內存使用情況,分別返回系統的總內存和閑置內存
不是通過V8分配的內存稱為堆外內存,利用堆外內存可以突破內存限制 的問題
3.Node的內存構成主要由通過V8進行分配的部分和Node自行分配的部分。受V8的垃圾回收限制的主要是V8的堆內存
D.內存泄漏
1.在Node中,緩存并非物美價廉,一旦一個對象被當做緩存來使用,那就意味著它將會常駐在老生代中。緩存中的鍵越多,長期存活的對象也就越多,這將導致垃圾回收在進行掃描和整理時,對這些對象做無用功
2.盡量使用外部緩存,如Redis和Memcached
3.隊列問題,如數據庫寫入操作的堆積:
表層解決方案是換用消費速度更高的技術
深層的解決方案應該是監控隊列的長度,一旦堆積,應當通過監控系統產生報警并通知相關人員
E.內存泄漏排查
1.node-heapdump、node-memwatch等工具
F.大內存應用
1.Node提供了stream處理大文件,如果不需要進行字符串層面的操作,則不需要V8來處理,可以嘗試進行純粹的Buffer操作,這不會受到V8內存堆的限制
六、理解Buffer
A.Buffer結構
1.Buffer是一個典型的JS與C++結合的模塊,它將性能相關部分用C++實現,將非性能相關的部分用JS實現
2.Buffer受Array類型的影響很大,可以訪問length屬性得到長度,也可以通過下標訪問元素;給元素的賦值如果小于0,就將該值逐次加到256,直到得到一個0到255之間的整數。如果得到的數值大于255,就逐次減256,如果是小數,舍棄小數部分
3.Node在內存的使用上應用的是在C++層面申請內存、在JS中分配內存的策略。Node采用了slab分配機制
B.Buffer的轉換
1.字符串:
new Buffer(str,[encoding]);
buf.toString([encoding],[start],[end]);
Buffer.isEncoding(encoding),判斷編碼是否支持轉換,可以使用iconv和iconv-lite庫
C.Buffer的拼接
1.Buffer不等于字符串,只是會隱式轉換??!需要注意編碼問題
2.setEncoding()只能處理utf8、Base64和UCS-2/UTF-16LE這3種編碼
3.用一個數組來存儲接收到的所有Buffer片段并記錄下所有片段的總長度,然后調用Buffer.concat()方法生成一個合并的Buffer對象。Buffer.concat()方法封裝了從小Buffer對象向大Buffer對象的復制過程。
D.Buffer與性能
1.通過預告轉換靜態內容為Buffer對象,可以有效地減少CPU的重復使用,節省服務器資源。在Node構建的Web應用中,可以選擇將頁面中的動態內容和靜態內容分離,靜態內容部分可以通過預先轉換為Buffer的方式,使性能得到提升。由于文件自身是二進制數據,所以在不需要改變內容的場景下,盡量只讀取Buffer,然后直接輸出,不做額外的轉換,避免損耗
2.highWaterMark的大小對性能的影響
highWaterMark設置對Buffer內存的分配和使用有一定影響
highWaterMark設置過濾,可能導致系統調用次數過多
3.如果文件較?。ㄐ∮?kb),有可能造成slab未能完全使用;對于大文件而言,highWaterMark的大小決定會觸發系統調用和data事件的次數;讀取一個相同的大文件時,highWaterMark值的大小與速度的關系:該值越大,讀取速度越快
七、網絡編程
A.構建TCP服務
1.服務器事件(net.createServer()):listening、connection、close、error
2.連接事件(net.connecct()):data、end、connect、drain、error、close、timeout
3.在Node中,由于TCP默認啟用了Nagle算法
B.構建UDP服務
1.UDP事件:message、listening、close、error
C.構建HTTP服務
1.http服務端事件:connection、request、close、checkContinue、connect、upgrade、clientError
2.http客戶端事件:response、socket、connect、upgrade、continue
D.構建WebSocket服務
E.網絡服務與安全
1.Node在網絡安全上提供了3個模塊,分別為crypto、tls和https
八、構建Web應用
1.Cookie優化:減小Cookie的大??;為靜態組件使用不同的域名;減少DNS的查詢;
2.緩存規則:添加Expires或Cache-Control到報文頭中;配置ETags;讓Ajax可緩存;
3.清除緩存:url請求后帶版本號,如http://xxx.com/?v=1.0.0
4.Content-Disposition,inline表示內容只需即時查看,attachment表示數據可以存為附件
九、玩轉進程
1.PHP的健壯性是由它給每個請求都建立獨立的上下文來實現的
2.Master-Worker模式,又稱主從模式。主進程不負責具體的業務處理,而是負責調度或管理工作進程,它是趨向于穩定的。工作進程負責具體的業務處理。
3.child_process模塊:
spawn():啟動一個子進程來執行命令
exec():與spawn()不同的是有一個回調函數獲子進程的狀況,可以指定timeout屬性設置超時時間,適合執行現有命令
execFile():啟動一個子進程來執行可執行文件,適合執行文件
Fork():創建Node的子進程只需要指定要執行的JS文件模塊即可
4.WebWorker允許創建工作線程并在后臺運行,使得一些阻塞較為嚴重的計算不影響主線程上的UI渲染
5.IPC(Inter-Process Communication,進程間通信),是為了讓不同的進程能夠互相訪問資源并進行協調工作,Node中使用的是管道(pipe)技術
6.句柄是一種可以用來標識資源的引用,它的內部包含了指向對象的文件描述符
7.Cluster事件:fork、online、listening、disconnect、exit、setup
十、測試
A.單元測試
1.編寫可測試代碼的原則:單一職責、接口抽象、層次分離
2.單元測試主要包含斷言、測試框架、測試用例、測試覆蓋率、mock、持續集成等,Node還會加入異步代碼測試和私有方法測試
3.斷言:是一種放在程序中的一階邏輯(如一個結果為真或是假的邏輯判斷式),目的是為了標示程序開發者預期的結果——當程序運行到斷言的位置時,對應的斷言應該為真。若斷言不為真,程序會中止運行,并出現錯誤信息
4.Node中的assert模塊包含:ok()、equal()、notEqual()、deepEqual()、notDeepEqual()、strictEqual()、notStrictEqual()、throws()、doesNotThrow()、ifError()
5.單元測試測試風格:
TDD(測試驅動開發):關注所有功能是否被正確實現,表述方式偏向于功能說明書的風格
BDD(行為驅動開發):關注整體行為是否符合預期,表述方式更接近于自然語言的習慣
6.相關工具:mocha、blanket、jscover、muk、Makefile、travis-ci
B.性能測試
1.基準測試:benchmark
2.壓力測試:ab、siege、http_load
十一、產品化
A.項目工程化
1.目錄結構 :只要遵循單一原則即可
2.構建工具:Makefile、Grunt
3.編碼規范:JSLint、JSHint
4.代碼審查
B.部署流程
1.在實際的項目需求中,有兩點需要驗證:一是功能的正確性,一是與數據相關的檢查
C.性能
1.拆分原則:做專一的事、讓擅長的工具做擅長的事情、將模型簡化、將風險分離
2.動靜分離、啟用緩存、多進程架構、讀寫分離
D.日志
1.訪問日志、異常日志、數據庫記錄、分割日志
E.監控報警
1.監控:日志監控、響應時間、進程監控、磁盤監控、內存監控、CPU占用監控、CPU load監控、I/O負載、網絡監控、應用狀態監控、DNS監控
2.報警的實現:郵件報警、短信或電話報警
F.穩定性
1.多機器:需要考慮負載均衡、狀態共享、數據一致性、反向代理
2.多機房
3.容災備份
G.異構共存
1.通過協議與已有的系統進行異構共存
附錄B.調試Node
1.Debugger
通過debugger;設置斷點
使用node debug xxxx.js
步進指令:cont或c、next或n、step或s、out或o、pause
2.Node Inspector
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。