遇到的問題
在使用Node.js開發(fā)應(yīng)用平臺時,有個需求:先從數(shù)據(jù)庫查詢參數(shù)帐姻,將其轉(zhuǎn)換為配置定義對象(Definition),然后存放在內(nèi)存中,通過一個定義管理器單例(Manager)供其他程序邏輯調(diào)用谜悟。如:并用于啟動子進程。
實現(xiàn)如上需求的編碼思路(僅僅給出偽代碼做問題討論)大致如下:
function load(key) {
// 1. 從數(shù)據(jù)庫查詢參數(shù)锨络,并轉(zhuǎn)換為定義對象
let config = mysql.query(`select * from table where name = ${key}`)
let defintion = new Definition(config)
// 2. 返回加載到的定義
return defintion
}
function doThing() {
// 3. 在需要時觸發(fā)定義加載赌躺,如:key='TEST'
let def = load('TEST')
// 4. 不符合預(yù)期:def為undefined,
console.log(def)
}
以上代碼邏輯看上去沒問題羡儿,但為什么def是undefined礼患?!
百度掠归,看了很多網(wǎng)友博客缅叠,才明白這是因為Node.js是非阻塞的,通過load()方法觸發(fā)了mysql.query()與數(shù)據(jù)庫交互屬于I/O事物虏冻,Node.js不會等待執(zhí)行結(jié)束肤粱,而是繼續(xù)執(zhí)行后續(xù)代碼,于是第2返回的defintion實際上是undefined厨相。因此3步得到的是undefined领曼。
改進 —— 邏輯上應(yīng)該等待load拿到定義對象后再save(),也就是需要進行同步處理蛮穿,可以做如下兩點改造:
// 改造1: load方法返回Promise對象
function load(key) {
return new Promise((resolve, reject) => {
// 1. 從數(shù)據(jù)庫查詢參數(shù)庶骄,并轉(zhuǎn)換為定義對象
let config = mysql.query(`select * from table where name = ${key}`)
let defintion = new Definition(config)
// 2. 返回加載到的定義
resolve(defintion)
})
}
// 改造2: 在doThing()上使用async/await,以確保load方法能同步拿到加載結(jié)果(async/await代碼可讀性優(yōu)于Promise/then)才執(zhí)行后續(xù)代碼
async function doThing(){
// 3. 在需要時觸發(fā)定義加載践磅,如:key='TEST'
let def = await load('TEST')
// 4. 滿足預(yù)期:def不再為undefined单刁,
console.log(def)
}
在Node.js的世界里,不同步的情況還有很多府适,如:setTimeout羔飞、setInterval、文件讀寫檐春、數(shù)據(jù)庫查詢逻淌、網(wǎng)絡(luò)請求http.on('connection',cb)等等
方案和原理介紹
針對問題以及Node.js的運行原理,看了網(wǎng)上找很多資料疟暖,摘抄內(nèi)容見《node-運行機制閱讀摘抄》恍风,似懂非懂,大體好像明白,但諸多細節(jié)不甚明了朋贬!
目前腦袋里對Node.js的認知限于——“打開冰箱門凯楔,大象放進去,關(guān)上冰箱門”锦募!于是看
Node.js官網(wǎng)資料摆屯。試著對Node.js的運行機制做更細微一些的理解!
先借用《Nodejs的運行原理-科普篇》一文對Node.js運行機制的比喻
...NodeJS在寒風中面對著10萬并發(fā)大軍糠亩,OK虐骑,沒問題,上來敵人一個扔到城里赎线,上來一個又扔到城里廷没。城里全民皆兵,可以很好地消化這些敵人...等民兵把敵人打個半死時垂寥,NodeJS再一刀斬于馬下颠黎!
作者用“敵人來了,扔進城里滞项,打個半死狭归,斬于馬下”這個故事過程比喻Node.js的運行機制!
其中:
“敵人”文判,在Node.js里分兩種:
- current operation过椎,也就是非異步操作,由主線程立刻執(zhí)行的代碼戏仓;
- Blocking疚宇, 異步操作,如:文件讀取赏殃、數(shù)據(jù)庫查詢敷待、Timer任務(wù)、網(wǎng)絡(luò)請求等等嗓奢。
“扔到城里”是Node.js對異步任務(wù)的分類
Node.js主線程不會直接處理Blocking類型的代碼,而是將其分類到不同的隊列浑厚,等后臺線程處理好了股耽,再執(zhí)行對應(yīng)的回調(diào),整個分類過程看起來如下圖:
執(zhí)行node xx.js時钳幅,V8解析xx.js代碼并放入執(zhí)行棧物蝙;
執(zhí)行棧和nextTick Queue中的內(nèi)容會在一次Tick周期內(nèi)被主線執(zhí)行 —— 主線程清空執(zhí)行棧后,立即處理nextTick Queue中的任務(wù)敢艰。
-
主線程處理Call Stack和nextTick Queue的過程構(gòu)成一個完整的Tick周期诬乞;
注意:Call Stack和nextTick Queue不屬于EventLoop周期內(nèi)的隊列;
-
EventLoop循環(huán)中,滿足執(zhí)行條件的回調(diào)會被Node.js放回調(diào)用棧(變?yōu)榱薱urrent operation)震嫉,執(zhí)行棧有內(nèi)容森瘪,則主線程開始一輪新的Tick周期將之處理
“放回執(zhí)行棧”這么說并不嚴謹票堵,但是有助于對下文Node.js運行機制的理解扼睬。
-
對于Blocing任務(wù)Node.js用用了下面幾種FIFO的隊列來分類:
-
Timer Queue
this phase executes callbacks scheduled by setTimeout() and setInterval().
-
Pending Callbacks Queue
executes I/O callbacks deferred to the next loop iteration.
-
Idle,prepare Queue
only used internally.
-
Poll Queue
retrieve new I/O events; execute I/O related callbacks (almost all with the exception of close callbacks, the ones scheduled by timers, and setImmediate()); node will block here when appropriate.
-
Immediate Queue
setImmediate() callbacks are invoked here.
-
Close Callbacks Queue
some close callbacks, e.g. socket.on('close', ...).
注意:上圖示意不意味著EventLoop執(zhí)行時檢查隊列的順序悴势,只是用來做任務(wù)分類示意窗宇。
-
“打個半死”表示異步任務(wù)已經(jīng)滿足執(zhí)行條件
“打”這個動作由Libuv的Thread Pool在后臺完成,流程如下圖:
工作線程(Work Thread)處理完某個異步任務(wù)特纤,會將數(shù)據(jù)綁定在callback函數(shù)上并放回事件隊列(Poll Queue)军俊。
“斬于馬下”表示Node.js處理了綁定了數(shù)據(jù)的callback回調(diào)函數(shù)
在EventLoop過程中,已完成的異步任務(wù)捧存,主線程將開啟一次新的Tick周期處理綁定了數(shù)據(jù)的回調(diào)函數(shù)粪躬。
EventLoop流程
除了異步I/O任務(wù),還有Timer任務(wù)矗蕊,實時收到的網(wǎng)絡(luò)請求等待短蜕,接下來看下完整一些的EventLoop流程,如下圖:
- 1傻咖、執(zhí)行命令node xx.js開始朋魔,V8引擎會將js腳本代碼解析并放入執(zhí)行棧(call stack),Node.js主線程就開始處理代碼了卿操,EventLoop開始警检,同時也開始一個Tick;
- 2.0害淤、主線程以后進先出的順序處理執(zhí)行棧中的代碼扇雕,Current operation當場處理;
- 2.1窥摄、如果是process.nextTick() 放到nextTick queue镶奉,等到執(zhí)行棧清空后,馬上處理崭放;
- 2.2哨苛、如果是I/O異步任務(wù)分放到Poll Queue,另外的按照任務(wù)分類規(guī)則放到對應(yīng)的隊列(線太多币砂,不一一畫了)建峭;
- 2.3、如果執(zhí)行棧為空則會處理nextTick Queue中的回調(diào)决摧,這些代碼是主線程同步處理的(所謂的nextTick就是指放在這個時機執(zhí)行的代碼)亿蒸。當nextTick Queue也被清空凑兰,則表示完成一個Tick周期(圖中 Tick Start - Tick End)。接著Node.js的EventLoop流程會進入Poll Queue的處理階段(EventLoop entry poll phase)边锁;
- 3.0姑食、進入Poll Queue執(zhí)行階段,Node.js首先檢查poll Queue是否為空砚蓬;
- 3.1矢门、不為空,繼續(xù)檢查是否超出最大運行poll循環(huán)限制(hard limit:根據(jù)操作系統(tǒng)不同的)灰蛙;
- 3.2祟剔、沒超出,則立刻同步方式處理這個回調(diào)邏輯(executing callbacks synchronously)摩梧,注意:執(zhí)行poll中的回調(diào)時物延,Node.js會將回調(diào)函數(shù)放到執(zhí)行棧中,進行一輪新的Tick處理仅父,每個回調(diào)一輪Tick叛薯;
- 3.3、結(jié)束一輪Tick笙纤,解決掉一個poll queue中的回調(diào)耗溜,回到3.0;
- 3.4省容、如果poll queue中的回調(diào)次數(shù)超過了硬件運行的數(shù)量限制抖拴,則報錯,終止Node.js的Event Loop腥椒;
報錯信息:RangeError: Maximum call stack size exceeded from v8
- 3.5阿宅、如果poll queue中的回調(diào)被處理完,也就是Poll Queue為空笼蛛,這時Node.js會先判斷immediate queue是否有內(nèi)容洒放,有,則進入Check phase滨砍。immediate queue中的內(nèi)容是在此前處理poll queue中任務(wù)的各輪Tick中放進來的往湿。
- 3.6、Node.js進入Check phase惋戏,按照先進先出的順序處理immediate queue中的回調(diào)领追,注意:同樣每個回調(diào)開一輪新的Tick處理,不過Node.js會連續(xù)處理完這個階段的所有回調(diào)函數(shù)(待分析清楚)日川。
- 3.7蔓腐、另外一種情況是不存在immediate矩乐,Node.js會跳過Check pahase龄句,進而判斷當前是否有已經(jīng)完成的I/O異步任務(wù)
- 3.8回论、有I/O任務(wù),則等待其執(zhí)行完成分歇;
- 3.9傀蓉、Libuv會將處理完成的I/O任務(wù)事件((回調(diào)函數(shù)和I/O異步任務(wù)獲得的數(shù)據(jù)一起))放回poll queue,這是poll queue不為空职抡,Node.js又按照3.0 - 3.3處理葬燎;
- 3.10、如果沒有I/O任務(wù)缚甩,Node.js會檢查是否有已經(jīng)滿足時點的Timer回調(diào)任務(wù)——指:setInterval谱净、setTimeout。
- 3.11擅威、沒有壕探,則回到poll phase繼續(xù)等待新I/O任務(wù) —— Libuv線程池處理好的事件,來自網(wǎng)絡(luò)的I/O事件等等郊丛,都會加入到poll queue中李请。
- 4.0、如果有到點的Timer回調(diào)厉熟,Node.js的EventLoop將進入Timer phase导盅,處理Timer Queue中滿足執(zhí)行條件的所有回調(diào)函數(shù),同樣每個回調(diào)一輪新的Tick揍瑟;
至此白翻,腦袋有一個相對清晰的Node.js運行流程模型:Event Loop大圈內(nèi)套了很多次Tick小圈,這些Tick小圈是Blocking任務(wù)滿足執(zhí)行條件時開啟的月培,如果沒有滿足執(zhí)行條件的Blocking任務(wù)嘁字,Node.js將停等待下一個滿足執(zhí)行條件的任務(wù)(3.8)!
寫代碼感受一下EventLoop和Tick
新建 s3.js杉畜,內(nèi)容如下:
console.log('0: 啟動Node.js纪蜒,開始了第一輪EventLoop,開始了第一輪Tick')
console.log('1: 第一輪Tick時此叠,第一個非阻塞函數(shù)(current function)')
console.log('2: 第一輪Tick時纯续,第二個非阻塞函數(shù)(current function)')
process.nextTick(()=>{ console.log('3: 第一輪Tick時,第一個放到nextTick階段的回調(diào)函數(shù)灭袁。執(zhí)行棧已空時執(zhí)行猬错,在nextTick中排序第一')})
setTimeout(()=>{console.log('13: 第一輪Tick時,第一個放入MessageQueue的setTimeout回調(diào)函數(shù) 延時10毫秒茸歧。終于到10毫秒了倦炒,Node.js新開一輪Tick執(zhí)行我')},10)
setTimeout(()=>{console.log('14: 第一輪Tick時,第二個放入MessageQueue的setTimeout回調(diào)函數(shù) 延時10毫秒软瞎。終于到10毫秒了逢唤,但是在MessageQueue中我排在13后拉讯。')},10)
setTimeout(()=>{
console.log('15: 第一輪Tick時,第三個放入MessageQueue的setTimeout回調(diào)函數(shù) 延時10毫秒鳖藕。終于到10毫秒了魔慷,但是在MessageQueue中我排在14后');
process.nextTick(()=>{
console.log('16: 輸出15步的Tick輪次時加入nextTick,該輪次結(jié)束時著恩,輸出了我院尔。')
console.log('17: 沒有任何Blocking任務(wù),Node.js結(jié)束EventLoop喉誊,退出Node.js')
})
},10)
setTimeout(()=>{
console.log('5: 第一輪Tick時邀摆,第四個放入MessageQueue的setTimeout回調(diào)函數(shù) 雖然是第四個放入MessageQueue的setTimeout,但延時0毫秒伍茄,第一輪Tick結(jié)束時Node.js檢查已滿足觸發(fā)條件隧熙,將對應(yīng)的回調(diào)函數(shù)放回執(zhí)行棧,因調(diào)用棧非空幻林,Node.js開始了新一輪Tick(第二輪)贞盯,本輸出發(fā)生在在第二輪Tick時');
process.nextTick(()=>{console.log('6: 第二輪Tick時,加入nextTick階段的回調(diào)函數(shù)沪饺。因為Node.js逐個檢查和執(zhí)行MessageQueue中的Job(處理過程見5)躏敢,因此第二輪Tick結(jié)束時立即執(zhí)行輸出')})},0)
setTimeout(()=>{
console.log('7: 第一輪Tick時,第五個放入MessageQueue的setTimeout回調(diào)函數(shù)整葡,雖然也是延時0毫秒件余,但是按MessageQueu的先進先出原則,Node.js在處理完5后遭居,才檢查這個Job啼器,處理過程同5,Node.js又開始輪新一輪Tick(第三輪)俱萍,本輸出發(fā)生在第三輪Tick時');
process.nextTick(()=>{console.log('8: 第三輪Tick時端壳,加入nextTick階段的回調(diào)函數(shù),因此第三輪Tick結(jié)束時立即執(zhí)行輸出')})},0) //FIXME nextTic再加入nextTick呢枪蘑?本輪损谦,還是下一輪Tick執(zhí)行
setImmediate(()=>{
setTimeout(()=>{console.log('xx: 我在可能在13前或16后輸出,根據(jù)Node.js的處理速度岳颇,在10毫秒以內(nèi)則輸出在13前照捡,大于10毫秒則輸出在16后,因為這個setTimeout排在MessageQueue最后话侧!')},0)
console.log('9: 第一輪Tick時栗精,第一個放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)。Node.js經(jīng)過以上三輪次Tick處理后瞻鹏,發(fā)現(xiàn)執(zhí)行棧為空悲立,且MessageQueue沒有滿足條件的Job需要處理赢赊,準備開始下一輪EventLoop處理。setImmediate的執(zhí)行時機就在下一輪EventLoop開始前级历,我又是第一個,Node.js會把回調(diào)函數(shù)放回執(zhí)行棧叭披,執(zhí)行棧非空寥殖,Node.js開始新一輪Tick(第四輪),本輸出發(fā)生在第四輪Tick時');
process.nextTick(()=>{console.log('10: 第四輪Tick時涩蜘,加入nextTick階段的回調(diào)函數(shù)嚼贡,因此第四輪Tick結(jié)束時立即執(zhí)行輸出')})
})
setImmediate(()=>{
console.log('11: 第一輪Tick時,第二個放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)同诫。執(zhí)行完9粤策,Node.js又開啟一輪Tick(第五輪)處理這個setImmediate。本輸出發(fā)生在第五輪Tick時')
process.nextTick(()=>{console.log('12: 第五輪Tick時误窖,加入nextTick階段的回調(diào)函數(shù)叮盘,因此第五輪Tick結(jié)束時立即執(zhí)行輸出')})
})
setTimeout(()=>{console.log('xx: 第一輪Tick時,第六個放入MessageQueue的setTimeout回調(diào)函數(shù)霹俺,輸出順序飄忽柔吼,但肯定都在nextTick之后。飄忽位置丙唧,取決于Node.js執(zhí)行到第XX輪Tick的耗時是否達到了2毫秒愈魏,如果達到,將在當前輪Tick結(jié)束時得到執(zhí)行想际。例如:第三輪Tick結(jié)束時培漏,Node.js的處理耗時已經(jīng)2毫秒,則我會輸出在8后(Node.js開始新一個Tick執(zhí)行我)')},2)
process.nextTick(()=>{ console.log('4: 第一輪Tick時胡本,第二個放到nextTick階段的回調(diào)函數(shù)牌柄。執(zhí)行棧已空時執(zhí)行,在nextTick中排序第二侧甫。第一輪Tick正式結(jié)束友鼻!')})
執(zhí)行命令 node s3.js 看具體結(jié)果(根據(jù)機器性能會稍有不同)
$ node s3.js
0: 啟動Node.js,開始了第一輪EventLoop闺骚,開始了第一輪Tick
1: 第一輪Tick時彩扔,第一個非阻塞函數(shù)(current function)
2: 第一輪Tick時,第二個非阻塞函數(shù)(current function)
3: 第一輪Tick時僻爽,第一個放到nextTick階段的回調(diào)函數(shù)虫碉。執(zhí)行棧已空時執(zhí)行,在nextTick中排序第一
4: 第一輪Tick時胸梆,第二個放到nextTick階段的回調(diào)函數(shù)敦捧。執(zhí)行棧已空時執(zhí)行须板,在nextTick中排序第二。第一輪Tick正式結(jié)束兢卵!
5: 第一輪Tick時习瑰,第四個放入MessageQueue的setTimeout回調(diào)函數(shù) 雖然是第四個放入MessageQueue的setTimeout,但延時0毫秒秽荤,第一輪Tick結(jié)束時Node.js檢查已滿足觸發(fā)條件甜奄,將對應(yīng)的回調(diào)函數(shù)放回執(zhí)行棧,因調(diào)用棧非空窃款,Node.js開始了新一輪Tick(第二輪)课兄,本輸出發(fā)生在在第二輪Tick時
6: 第二輪Tick時,加入nextTick階段的回調(diào)函數(shù)晨继。因為Node.js逐個檢查和執(zhí)行MessageQueue中的Job(處理過程見5)烟阐,因此第二輪Tick結(jié)束時立即執(zhí)行輸出
7: 第一輪Tick時,第五個放入MessageQueue的setTimeout回調(diào)函數(shù)紊扬,雖然也是延時0毫秒蜒茄,但是按MessageQueu的先進先出原則,Node.js在處理完5后餐屎,才檢查這個Job扩淀,處理過程同5,Node.js又開始輪新一輪Tick(第三輪)啤挎,本輸出發(fā)生在第三輪Tick時
8: 第三輪Tick時驻谆,加入nextTick階段的回調(diào)函數(shù),因此第三輪Tick結(jié)束時立即執(zhí)行輸出
9: 第一輪Tick時庆聘,第一個放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)胜臊。Node.js經(jīng)過以上三輪次Tick處理后,發(fā)現(xiàn)執(zhí)行棧為空伙判,且MessageQueue沒有滿足條件的Job需要處理象对,準備開始下一輪EventLoop處理。setImmediate的執(zhí)行時機就在下一輪EventLoop開始前宴抚,我又是第一個勒魔,Node.js會把回調(diào)函數(shù)放回執(zhí)行棧,執(zhí)行棧非空菇曲,Node.js開始新一輪Tick(第四輪)冠绢,本輸出發(fā)生在第四輪Tick時
10: 第四輪Tick時,加入nextTick階段的回調(diào)函數(shù)常潮,因此第四輪Tick結(jié)束時立即執(zhí)行輸出
11: 第一輪Tick時弟胀,第二個放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)杉适。執(zhí)行完9吁断,Node.js又開啟一輪Tick(第五輪)處理這個setImmediate笆包。本輸出發(fā)生在第五輪Tick時
12: 第五輪Tick時憾股,加入nextTick階段的回調(diào)函數(shù),因此第五輪Tick結(jié)束時立即執(zhí)行輸出
xx: 第一輪Tick時夏哭,第六個放入MessageQueue的setTimeout回調(diào)函數(shù)检柬,輸出順序飄忽,但肯定都在nextTick之后竖配。飄忽位置何址,取決于Node.js執(zhí)行到第XX輪Tick的耗時是否達到了2毫秒,如果達到械念,將在當前輪Tick結(jié)束時得到執(zhí)行。例如:第三輪Tick結(jié)束時运悲,Node.js的處理耗時已經(jīng)2毫秒龄减,則我會輸出在8后(Node.js開始新一個Tick執(zhí)行我)
xx: 我在可能在13前或16后輸出,根據(jù)Node.js的處理速度班眯,在10毫秒以內(nèi)則輸出在13前希停,大于10毫秒則輸出在16后,因為這個setTimeout排在MessageQueue最后署隘!
13: 第一輪Tick時宠能,第一個放入MessageQueue的setTimeout回調(diào)函數(shù) 延時10毫秒。終于到10毫秒了磁餐,Node.js新開一輪Tick執(zhí)行我
14: 第一輪Tick時违崇,第二個放入MessageQueue的setTimeout回調(diào)函數(shù) 延時10毫秒。終于到10毫秒了诊霹,但是在MessageQueue中我排在13后羞延。
15: 第一輪Tick時,第三個放入MessageQueue的setTimeout回調(diào)函數(shù) 延時10毫秒脾还。終于到10毫秒了伴箩,但是在MessageQueue中我排在14后
16: 輸出15步的Tick輪次時加入nextTick,該輪次結(jié)束時鄙漏,輸出了我嗤谚。
17: 沒有任何Blocking任務(wù),Node.js結(jié)束EventLoop怔蚌,退出Node.js
看完代碼運行結(jié)果巩步,集中精力看下圖中的「Event Loop」,體會一下EventLoop桦踊,一輪完整的EventLoop周期是怎么樣的渗钉!
Node.js方案的優(yōu)點
適合高并發(fā)場景!
Node 公開宣稱的目標是 “旨在提供一種簡單的構(gòu)建可伸縮網(wǎng)絡(luò)程序的方法”。我們來看一個簡單的例子鳄橘,在 Java和 PHP 這類語言中声离,每個連接都會生成一個新線程,每個新線程可能需要 2 MB 的配套內(nèi)存瘫怜。在一個擁有 8 GB RAM 的系統(tǒng)上术徊,理論上最大的并發(fā)連接數(shù)量是 4,000 個用戶。隨著您的客戶群的增長鲸湃,如果希望您的 Web 應(yīng)用程序支持更多用戶赠涮,那么,您必須添加更多服務(wù)器暗挑。所以在傳統(tǒng)的后臺開發(fā)中笋除,整個 Web 應(yīng)用程序架構(gòu)(包括流量、處理器速度和內(nèi)存速度)中的瓶頸是:服務(wù)器能夠處理的并發(fā)連接的最大數(shù)量炸裆。這個不同的架構(gòu)承載的并發(fā)數(shù)量是不一致的垃它。
而Node的出現(xiàn)就是為了解決這個問題:更改連接到服務(wù)器的方式。
在Node 聲稱它不允許使用鎖烹看,它不會直接阻塞 I/O 調(diào)用国拇。Node在每個連接發(fā)射一個在 Node 引擎的進程中運行的事件,而不是為每個連接生成一個新的 OS 線程(并為其分配一些配套內(nèi)存)惯殊。
Node.js方案的缺點
不適合CPU密集型處理酱吝!
如上所述,nodejs的機制是單線程土思,這個線程里面务热,有一個事件循環(huán)機制,處理所有的請求己儒。在事件處理過程中陕习,它會智能地將一些涉及到IO、網(wǎng)絡(luò)通信等耗時比較長的操作址愿,交由worker threads去執(zhí)行该镣,執(zhí)行完了再回調(diào),這就是所謂的異步IO非阻塞响谓。但是损合,那些非IO操作,只用CPU計算的操作娘纷,它就自己扛了嫁审,比如算什么斐波那契數(shù)列之類。它是單線程赖晶,這些自己扛的任務(wù)要一個接著一個地完成律适,前面那個沒完成辐烂,后面的只能干等。
因此捂贿,對CPU要求比較高的CPU密集型任務(wù)多的話纠修,就有可能會造成號稱高性能,適合高并發(fā)的node.js服務(wù)器反應(yīng)緩慢厂僧。
相對而已CPU密集型的場景可以選用Apache——Apache具有多線程高并發(fā)共享內(nèi)存地址空間的特性扣草,那就意味著如果服務(wù)器足夠強大,處理器足夠高核颜屠,Apache的運作將會非常良好辰妙,所以適用于(并發(fā))異步處理相對較少,后臺計算量大甫窟,后臺業(yè)務(wù)邏輯復(fù)雜的應(yīng)用程序密浑。
數(shù)據(jù)密集型:Data-Intensive applications,數(shù)據(jù)是其主要挑戰(zhàn)(數(shù)據(jù)量粗井,數(shù)據(jù)復(fù)雜度尔破,數(shù)據(jù)變化速度),與之相對的是計算密集型背传,即處理器速度是其瓶頸〈粽埃現(xiàn)今很多數(shù)據(jù)都是數(shù)據(jù)密集型的台夺,而非計算密集型径玖,CPU很少成為瓶頸。數(shù)據(jù)密集型應(yīng)用
適用場景舉例
既然NodeJS處理并發(fā)的能力強颤介,但處理計算和邏輯的能力反而很弱梳星,因此,如果我們把復(fù)雜的邏輯運算都搬到前端(客戶端)完成滚朵,而NodeJS只需要提供異步I/O冤灾,這樣就可以實現(xiàn)對高并發(fā)的高性能處理。
這樣的場景有很多辕近,比如:
1韵吨、RESTful API
這是適合 Node 的理想情況,因為您可以構(gòu)建它來處理數(shù)萬條連接移宅。它仍然不需要大量邏輯归粉;它本質(zhì)上只是從某個數(shù)據(jù)庫中查找一些值并將它們組成一個響應(yīng)。由于響應(yīng)是少量文本漏峰,入站請求也是少量的文本糠悼,因此流量不高,一臺機器甚至也可以處理最繁忙的公司的 API 需求浅乔。完成數(shù)據(jù)型應(yīng)用中對數(shù)據(jù)的獲取功能倔喂。
2、實時程序
比如聊天服務(wù)
聊天應(yīng)用程序是最能體現(xiàn) Node.js 優(yōu)點的例子:輕量級、高流量并且能良好的應(yīng)對跨平臺設(shè)備上運行密集型數(shù)據(jù)(雖然計算能力低)席噩。同時班缰,聊天也是一個非常值得學習的用例,因為它很簡單班挖,并且涵蓋了目前為止一個典型的 Node.js 會用到的大部分解決方案鲁捏。
3、單頁APP
客戶端邏輯強大的單頁APP萧芙,比如說:本地化的在線音樂應(yīng)用给梅,本地化的在線搜索應(yīng)用,本地化的在線APP等双揪。
ajax很多《穑現(xiàn)在單頁的機制似乎很流行,比如phonegap做出來的APP渔期,一個頁面包打天下的例子比比皆是运吓。
總而言之,NodeJS適合運用在高并發(fā)疯趟、I/O密集拘哨、少量業(yè)務(wù)邏輯(只有一個線程)的場景;
參考資料
參考資料:
-
Introduction to Node.js(官網(wǎng))
A Node.js app is run in a single process, without creating a new thread for every request. Node.js provides a set of asynchronous I/O primitives in its standard library that prevent JavaScript code from blocking and generally, libraries in Node.js are written using non-blocking paradigms, making blocking behavior the exception rather than the norm.
-
Node是一個服務(wù)器端JavaScript解釋器信峻,用于方便地搭建響應(yīng)速度快倦青、易于擴展的網(wǎng)絡(luò)應(yīng)用。Node使用事件驅(qū)動盹舞,非阻塞I/O 模型而得以輕量和高效产镐,非常適合在分布式設(shè)備上運行數(shù)據(jù)密集型的實時應(yīng)用。Node是一個可以讓JavaScript運行在瀏覽器之外的平臺踢步。它實現(xiàn)了諸如文件系統(tǒng)癣亚、模塊、包获印、操作系統(tǒng) API述雾、網(wǎng)絡(luò)通信等Core JavaScript沒有或者不完善的功能。歷史上將JavaScript移植到瀏覽器外的計劃不止一個兼丰,但Node.js 是最出色的一個玻孟。