nodejs的運行機制

遇到的問題

在使用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),整個分類過程看起來如下圖:

Nodejs運行機制33.png
  • 執(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在后臺完成,流程如下圖:

image.png

工作線程(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流程,如下圖:

nodejs-EventLoop處理流程.jpg
  • 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周期是怎么樣的渗钉!

image.png

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.

  • The Node.js EventLoop

  • Discover JavaScript Timers

  • Node.js是單線程

  • Node.js的事件驅(qū)動和非阻塞I/O

  • Nodejs的運行原理-科普篇

    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 是最出色的一個玻孟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市地粪,隨后出現(xiàn)的幾起案子取募,更是在濱河造成了極大的恐慌,老刑警劉巖蟆技,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玩敏,死亡現(xiàn)場離奇詭異斗忌,居然都是意外死亡,警方通過查閱死者的電腦和手機旺聚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門织阳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砰粹,你說我怎么就攤上這事唧躲。” “怎么了碱璃?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵弄痹,是天一觀的道長。 經(jīng)常有香客問我嵌器,道長肛真,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任爽航,我火速辦了婚禮蚓让,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讥珍。我一直安慰自己历极,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布衷佃。 她就那樣靜靜地躺著趟卸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纲酗。 梳的紋絲不亂的頭發(fā)上衰腌,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天新蟆,我揣著相機與錄音觅赊,去河邊找鬼。 笑死琼稻,一個胖子當著我的面吹牛吮螺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帕翻,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼鸠补,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嘀掸?” 一聲冷哼從身側(cè)響起紫岩,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎睬塌,沒想到半個月后泉蝌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歇万,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年勋陪,在試婚紗的時候發(fā)現(xiàn)自己被綠了贪磺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡诅愚,死狀恐怖寒锚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情违孝,我是刑警寧澤刹前,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站雌桑,受9級特大地震影響腮郊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜筹燕,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一轧飞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撒踪,春花似錦过咬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至耕捞,卻和暖如春衔掸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俺抽。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工敞映, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人磷斧。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓振愿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弛饭。 傳聞我的和親對象是個殘疾皇子冕末,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容