什么是Node.js
Node.js是一個JavaScript運行環(huán)境堡妒,可以把它理解為前端中的Chrome瀏覽器踱稍,同時它又是跨平臺的党饮。同時由于Node.js核心模塊支持網(wǎng)絡(luò)通信、文件讀寫等操作评甜,配合其他數(shù)據(jù)庫提供的驅(qū)動模塊即可編寫服務(wù)端程序活尊。
優(yōu)缺點
優(yōu)點:
1.強大的并發(fā)能力隶校。它的事件循環(huán)機制使得Node.js在執(zhí)行代碼時不會被耗時的I/O操作(數(shù)據(jù)庫查詢漏益、文件讀寫)所阻塞,可以瞬間處理大量的客戶端請求深胳,而不像大多數(shù)多線程服務(wù)器端那樣受到線程數(shù)量及I/O操作耗時的影響绰疤。
2.豐富的模塊,并且主流的數(shù)據(jù)庫都提供了Node.js的驅(qū)動程序舞终。
缺點:
1.CPU阻塞轻庆。雖然I/O操作不會阻塞Node.js程序,但是CPU密集型的操作卻可以敛劝,因為它是單線程的余爆,無法使用多線程來分擔(dān)計算壓力。
2.穩(wěn)定性低攘蔽。單進程單線程使得Node.js在穩(wěn)定性方面不如多線程的服務(wù)器龙屉,因為一旦代碼出現(xiàn)嚴(yán)重錯誤或者漏洞導(dǎo)致報錯后,整個線程將無法繼續(xù)工作满俗,不過現(xiàn)在已經(jīng)有很多方案來幫助解決這個問題转捕,例如pm2。
Node.js的結(jié)構(gòu)
Node.js 的結(jié)構(gòu)大致分為三個層次:
- Node.js 標(biāo)準(zhǔn)庫唆垃,這部分是由 Javascript 編寫的五芝,即我們使用過程中直接能調(diào)用的 API。
- Node.js bindings辕万、C++ Addons
Node.js的底層代碼是用C++寫的枢步,并引用了C++的一些庫,但是不同語言之間不能直接互相調(diào)用渐尿,所以通過bindings將C++寫的核心庫轉(zhuǎn)成JS醉途,Addons的作用類型,只不過它是針對第三方C++庫的砖茸。 - V8隘擎、Libuv、C-ares凉夯、http_parser货葬、OpenSSL、zlib 等
V8:Google 推出的 Javascript VM劲够,也是 Node.js 為什么使用的是 Javascript 的關(guān)鍵震桶,它為 Javascript 提供了在非瀏覽器端運行的環(huán)境,它的高效是 Node.js 之所以高效的原因之一征绎。
Libuv:它為 Node.js 提供了跨平臺蹲姐,線程池,事件池,異步 I/O 等能力淤堵,是 Node.js 如此強大的關(guān)鍵寝衫。
C-ares:提供了異步處理 DNS 相關(guān)的能力顷扩。
http_parser拐邪、OpenSSL、zlib 等:提供包括 http 解析隘截、SSL扎阶、數(shù)據(jù)壓縮等其他的能力。
Node.js運行機制
nodejs采用單線程事件循環(huán)的工作機制婶芭。
進程與線程
首先要了解一下進程與線程东臀,我們在操作系統(tǒng)中啟動應(yīng)用程序的時候,就會創(chuàng)建一個或多個進程犀农,比如打開Word惰赋,就創(chuàng)建了Word進程;一個進程可能要做很多事呵哨,相當(dāng)于一個個子任務(wù)赁濒,這就是線程,進程是線程的容器孟害。同一個進程中的線程之間可以共享資源拒炎,而進程之間的數(shù)據(jù)是相互隔離的,只有建立了 IPC 通信挨务,進程之間才可數(shù)據(jù)共享击你。
還有一種理解是進程和線程都是一個時間段的描述,是CPU工作時間段的描述谎柄。CPU的運行速度很快丁侄,在CPU看來所有任務(wù)都是一個個輪流執(zhí)行的,運行一個程序時朝巫,首先是加載程序的執(zhí)行環(huán)境上下文鸿摇,然后執(zhí)行程序,最后保存它的上下文捍歪,再運行下一個程序户辱。所以進程就是執(zhí)行時間總和 = CPU加載上下文+CPU執(zhí)行+CPU保存上下文。然而進程的顆粒度太大糙臼,如果我們把進程比喻為一個運行在電腦上的軟件庐镐,一個軟件的執(zhí)行不可能是一條邏輯執(zhí)行的,必定有多個分支或多個程序段变逃,那么這里的每一部分就是一個線程必逆,每一個線程共享了進程的上下文環(huán)境,它也是更為細小的CPU時間段。
傳統(tǒng)的web服務(wù)器端是多線程的名眉,利用多線程來處理多請求粟矿,服務(wù)端進程負責(zé)將每個請求任務(wù)分發(fā)給單個線程進行處理。首先服務(wù)端會維護一個線程池损拢,當(dāng)客戶端發(fā)送請求給服務(wù)端時陌粹,會從線程池中取出線程,將請求分派給它處理福压;處理請求時掏秩,很可能需要讀寫數(shù)據(jù)庫,這種I/O操作一般會消耗一定的時間造成I/O阻塞荆姆,線程讀寫完數(shù)據(jù)庫后將結(jié)果返回蒙幻,線程被釋放,回到線程池中等待下次任務(wù)胆筒。在這種模式下邮破,一個線程只能處理一個請求,一直到處理完成才能處理下一個請求仆救,如果當(dāng)前所有線程都在忙著等待I/O操作返回結(jié)果而阻塞抒和,那么新來的請求就無法處理。此時一般會在服務(wù)器端創(chuàng)建一個請求隊列派桩,并按照順序分派給可用的線程构诚。
在Node.js中單線程執(zhí)行的優(yōu)勢就是減少了創(chuàng)建、切換線程的開銷铆惑,執(zhí)行速度相對更快范嘱;內(nèi)存占用少;并且不會因為多個線程同時讀寫一個文件而產(chǎn)生問題员魏;
但是單線程的容錯性差丑蛤,如果一個線程出錯會導(dǎo)致整個進程崩潰,并且CPU利用率也不足撕阎,單線程只利用了CPU的一個核受裹,另外遇到CPU密集型的操作時也會造成阻塞。
事件循環(huán)
如果按照上面的處理方式虏束,那單線程豈不是一次只能處理一個請求棉饶?當(dāng)然不是,它還有一個運行機制镇匀,叫做事件循環(huán)照藻,過程如下:
1.Node.js服務(wù)端啟動并初始化一個事件隊列,然后創(chuàng)建一個事件循環(huán)汗侵,它是無限循環(huán)的單線程幸缕。
2.客戶端發(fā)起請求群发。
3.服務(wù)端接收請求并放入事件隊列。
4.事件循環(huán)檢查事件隊列发乔,采取先進先出的原則取出請求熟妓,判斷請求是否會阻塞I/O,如果會就從底層的線程池中取出線程來處理請求(底層是C++)栏尚,事件循環(huán)繼續(xù)執(zhí)行起愈。如果不會阻塞,那么事件循環(huán)線程處理請求抵栈。
5.線程處理完請求后返回結(jié)果告材。
這種方式和多線程直接處理多請求的方式的優(yōu)勢在于坤次,當(dāng)線程池中的線程都被阻塞時古劲,事件循環(huán)線程不受影響,當(dāng)有新的請求過來時缰猴,如果是非阻塞I/O請求产艾,事件循環(huán)線程可以直接處理并返回結(jié)果。