nodejs的運(yùn)行機(jī)制

遇到的問題

在使用Node.js開發(fā)應(yīng)用平臺(tái)時(shí),有個(gè)需求:先從數(shù)據(jù)庫(kù)查詢參數(shù),將其轉(zhuǎn)換為配置定義對(duì)象(Definition),然后存放在內(nèi)存中,通過一個(gè)定義管理器單例(Manager)供其他程序邏輯調(diào)用。如:并用于啟動(dòng)子進(jìn)程。

實(shí)現(xiàn)如上需求的編碼思路(僅僅給出偽代碼做問題討論)大致如下:


function load(key) {
    // 1. 從數(shù)據(jù)庫(kù)查詢參數(shù),并轉(zhuǎn)換為定義對(duì)象
    let config = mysql.query(`select * from table where name = ${key}`)
    let defintion = new Definition(config)
    // 2. 返回加載到的定義
    return defintion
}

function doThing() {

    // 3. 在需要時(shí)觸發(fā)定義加載,如:key='TEST'
    let def = load('TEST')

    // 4. 不符合預(yù)期:def為undefined,
    console.log(def)
}

以上代碼邏輯看上去沒問題,但為什么def是undefined?!

百度,看了很多網(wǎng)友博客,才明白這是因?yàn)镹ode.js是非阻塞的,通過load()方法觸發(fā)了mysql.query()與數(shù)據(jù)庫(kù)交互屬于I/O事物,Node.js不會(huì)等待執(zhí)行結(jié)束,而是繼續(xù)執(zhí)行后續(xù)代碼,于是第2返回的defintion實(shí)際上是undefined。因此3步得到的是undefined。

改進(jìn) —— 邏輯上應(yīng)該等待load拿到定義對(duì)象后再save(),也就是需要進(jìn)行同步處理,可以做如下兩點(diǎn)改造:


// 改造1: load方法返回Promise對(duì)象
function load(key) {
    return new Promise((resolve, reject) => {
        // 1. 從數(shù)據(jù)庫(kù)查詢參數(shù),并轉(zhuǎn)換為定義對(duì)象
        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. 在需要時(shí)觸發(fā)定義加載,如:key='TEST'
    let def = await load('TEST')

    // 4. 滿足預(yù)期:def不再為undefined,
    console.log(def)
}

在Node.js的世界里,不同步的情況還有很多,如:setTimeout、setInterval、文件讀寫、數(shù)據(jù)庫(kù)查詢、網(wǎng)絡(luò)請(qǐng)求http.on('connection',cb)等等

方案和原理介紹

針對(duì)問題以及Node.js的運(yùn)行原理,看了網(wǎng)上找很多資料,摘抄內(nèi)容見《node-運(yùn)行機(jī)制閱讀摘抄》,似懂非懂,大體好像明白,但諸多細(xì)節(jié)不甚明了!

目前腦袋里對(duì)Node.js的認(rèn)知限于——“打開冰箱門,大象放進(jìn)去,關(guān)上冰箱門”!于是看
Node.js官網(wǎng)資料。試著對(duì)Node.js的運(yùn)行機(jī)制做更細(xì)微一些的理解!

先借用《Nodejs的運(yùn)行原理-科普篇》一文對(duì)Node.js運(yùn)行機(jī)制的比喻

...NodeJS在寒風(fēng)中面對(duì)著10萬并發(fā)大軍,OK,沒問題,上來敵人一個(gè)扔到城里,上來一個(gè)又扔到城里。城里全民皆兵,可以很好地消化這些敵人...等民兵把敵人打個(gè)半死時(shí),NodeJS再一刀斬于馬下!

作者用“敵人來了,扔進(jìn)城里,打個(gè)半死,斬于馬下”這個(gè)故事過程比喻Node.js的運(yùn)行機(jī)制!

其中:

“敵人”,在Node.js里分兩種:

  • current operation,也就是非異步操作,由主線程立刻執(zhí)行的代碼;
  • Blocking, 異步操作,如:文件讀取、數(shù)據(jù)庫(kù)查詢、Timer任務(wù)、網(wǎng)絡(luò)請(qǐng)求等等。

“扔到城里”是Node.js對(duì)異步任務(wù)的分類

Node.js主線程不會(huì)直接處理Blocking類型的代碼,而是將其分類到不同的隊(duì)列,等后臺(tái)線程處理好了,再執(zhí)行對(duì)應(yīng)的回調(diào),整個(gè)分類過程看起來如下圖:

Nodejs運(yùn)行機(jī)制33.png
  • 執(zhí)行node xx.js時(shí),V8解析xx.js代碼并放入執(zhí)行棧;

  • 執(zhí)行棧和nextTick Queue中的內(nèi)容會(huì)在一次Tick周期內(nèi)被主線執(zhí)行 —— 主線程清空?qǐng)?zhí)行棧后,立即處理nextTick Queue中的任務(wù)。

  • 主線程處理Call Stack和nextTick Queue的過程構(gòu)成一個(gè)完整的Tick周期;

    注意:Call Stack和nextTick Queue不屬于EventLoop周期內(nèi)的隊(duì)列;

  • EventLoop循環(huán)中,滿足執(zhí)行條件的回調(diào)會(huì)被Node.js放回調(diào)用棧(變?yōu)榱薱urrent operation),執(zhí)行棧有內(nèi)容,則主線程開始一輪新的Tick周期將之處理

    “放回執(zhí)行棧”這么說并不嚴(yán)謹(jǐn),但是有助于對(duì)下文Node.js運(yùn)行機(jī)制的理解。

  • 對(duì)于Blocing任務(wù)Node.js用用了下面幾種FIFO的隊(duì)列來分類:

    • 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í)行時(shí)檢查隊(duì)列的順序,只是用來做任務(wù)分類示意。

“打個(gè)半死”表示異步任務(wù)已經(jīng)滿足執(zhí)行條件

“打”這個(gè)動(dòng)作由Libuv的Thread Pool在后臺(tái)完成,流程如下圖:

image.png

工作線程(Work Thread)處理完某個(gè)異步任務(wù),會(huì)將數(shù)據(jù)綁定在callback函數(shù)上并放回事件隊(duì)列(Poll Queue)。

“斬于馬下”表示Node.js處理了綁定了數(shù)據(jù)的callback回調(diào)函數(shù)

在EventLoop過程中,已完成的異步任務(wù),主線程將開啟一次新的Tick周期處理綁定了數(shù)據(jù)的回調(diào)函數(shù)。

EventLoop流程

除了異步I/O任務(wù),還有Timer任務(wù),實(shí)時(shí)收到的網(wǎng)絡(luò)請(qǐng)求等待,接下來看下完整一些的EventLoop流程,如下圖:

nodejs-EventLoop處理流程.jpg
  • 1、執(zhí)行命令node xx.js開始,V8引擎會(huì)將js腳本代碼解析并放入執(zhí)行棧(call stack),Node.js主線程就開始處理代碼了,EventLoop開始,同時(shí)也開始一個(gè)Tick;
  • 2.0、主線程以后進(jìn)先出的順序處理執(zhí)行棧中的代碼,Current operation當(dāng)場(chǎng)處理;
  • 2.1、如果是process.nextTick() 放到nextTick queue,等到執(zhí)行棧清空后,馬上處理;
  • 2.2、如果是I/O異步任務(wù)分放到Poll Queue,另外的按照任務(wù)分類規(guī)則放到對(duì)應(yīng)的隊(duì)列(線太多,不一一畫了);
  • 2.3、如果執(zhí)行棧為空則會(huì)處理nextTick Queue中的回調(diào),這些代碼是主線程同步處理的(所謂的nextTick就是指放在這個(gè)時(shí)機(jī)執(zhí)行的代碼)。當(dāng)nextTick Queue也被清空,則表示完成一個(gè)Tick周期(圖中 Tick Start - Tick End)。接著Node.js的EventLoop流程會(huì)進(jìn)入Poll Queue的處理階段(EventLoop entry poll phase);
  • 3.0、進(jìn)入Poll Queue執(zhí)行階段,Node.js首先檢查poll Queue是否為空;
  • 3.1、不為空,繼續(xù)檢查是否超出最大運(yùn)行poll循環(huán)限制(hard limit:根據(jù)操作系統(tǒng)不同的);
  • 3.2、沒超出,則立刻同步方式處理這個(gè)回調(diào)邏輯(executing callbacks synchronously),注意:執(zhí)行poll中的回調(diào)時(shí),Node.js會(huì)將回調(diào)函數(shù)放到執(zhí)行棧中,進(jìn)行一輪新的Tick處理,每個(gè)回調(diào)一輪Tick;
  • 3.3、結(jié)束一輪Tick,解決掉一個(gè)poll queue中的回調(diào),回到3.0;
  • 3.4、如果poll queue中的回調(diào)次數(shù)超過了硬件運(yùn)行的數(shù)量限制,則報(bào)錯(cuò),終止Node.js的Event Loop;

    報(bào)錯(cuò)信息:RangeError: Maximum call stack size exceeded from v8

  • 3.5、如果poll queue中的回調(diào)被處理完,也就是Poll Queue為空,這時(shí)Node.js會(huì)先判斷immediate queue是否有內(nèi)容,有,則進(jìn)入Check phase。immediate queue中的內(nèi)容是在此前處理poll queue中任務(wù)的各輪Tick中放進(jìn)來的。
  • 3.6、Node.js進(jìn)入Check phase,按照先進(jìn)先出的順序處理immediate queue中的回調(diào),注意:同樣每個(gè)回調(diào)開一輪新的Tick處理,不過Node.js會(huì)連續(xù)處理完這個(gè)階段的所有回調(diào)函數(shù)(待分析清楚)。
  • 3.7、另外一種情況是不存在immediate,Node.js會(huì)跳過Check pahase,進(jìn)而判斷當(dāng)前是否有已經(jīng)完成的I/O異步任務(wù)
  • 3.8、有I/O任務(wù),則等待其執(zhí)行完成;
  • 3.9、Libuv會(huì)將處理完成的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會(huì)檢查是否有已經(jīng)滿足時(shí)點(diǎn)的Timer回調(diào)任務(wù)——指:setInterval、setTimeout。
  • 3.11、沒有,則回到poll phase繼續(xù)等待新I/O任務(wù) —— Libuv線程池處理好的事件,來自網(wǎng)絡(luò)的I/O事件等等,都會(huì)加入到poll queue中。
  • 4.0、如果有到點(diǎn)的Timer回調(diào),Node.js的EventLoop將進(jìn)入Timer phase,處理Timer Queue中滿足執(zhí)行條件的所有回調(diào)函數(shù),同樣每個(gè)回調(diào)一輪新的Tick;

至此,腦袋有一個(gè)相對(duì)清晰的Node.js運(yùn)行流程模型:Event Loop大圈內(nèi)套了很多次Tick小圈,這些Tick小圈是Blocking任務(wù)滿足執(zhí)行條件時(shí)開啟的,如果沒有滿足執(zhí)行條件的Blocking任務(wù),Node.js將停等待下一個(gè)滿足執(zhí)行條件的任務(wù)(3.8)!

寫代碼感受一下EventLoop和Tick

新建 s3.js,內(nèi)容如下:


console.log('0: 啟動(dòng)Node.js,開始了第一輪EventLoop,開始了第一輪Tick')
console.log('1: 第一輪Tick時(shí),第一個(gè)非阻塞函數(shù)(current function)')
console.log('2: 第一輪Tick時(shí),第二個(gè)非阻塞函數(shù)(current function)')
process.nextTick(()=>{ console.log('3: 第一輪Tick時(shí),第一個(gè)放到nextTick階段的回調(diào)函數(shù)。執(zhí)行棧已空時(shí)執(zhí)行,在nextTick中排序第一')})

setTimeout(()=>{console.log('13: 第一輪Tick時(shí),第一個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 延時(shí)10毫秒。終于到10毫秒了,Node.js新開一輪Tick執(zhí)行我')},10)
setTimeout(()=>{console.log('14: 第一輪Tick時(shí),第二個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 延時(shí)10毫秒。終于到10毫秒了,但是在MessageQueue中我排在13后。')},10)
setTimeout(()=>{
    console.log('15: 第一輪Tick時(shí),第三個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 延時(shí)10毫秒。終于到10毫秒了,但是在MessageQueue中我排在14后');
    process.nextTick(()=>{
        console.log('16: 輸出15步的Tick輪次時(shí)加入nextTick,該輪次結(jié)束時(shí),輸出了我。')
        console.log('17: 沒有任何Blocking任務(wù),Node.js結(jié)束EventLoop,退出Node.js')
    })
},10)
setTimeout(()=>{
    console.log('5: 第一輪Tick時(shí),第四個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 雖然是第四個(gè)放入MessageQueue的setTimeout,但延時(shí)0毫秒,第一輪Tick結(jié)束時(shí)Node.js檢查已滿足觸發(fā)條件,將對(duì)應(yīng)的回調(diào)函數(shù)放回執(zhí)行棧,因調(diào)用棧非空,Node.js開始了新一輪Tick(第二輪),本輸出發(fā)生在在第二輪Tick時(shí)');
    process.nextTick(()=>{console.log('6: 第二輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù)。因?yàn)镹ode.js逐個(gè)檢查和執(zhí)行MessageQueue中的Job(處理過程見5),因此第二輪Tick結(jié)束時(shí)立即執(zhí)行輸出')})},0) 
setTimeout(()=>{
    console.log('7: 第一輪Tick時(shí),第五個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù),雖然也是延時(shí)0毫秒,但是按MessageQueu的先進(jìn)先出原則,Node.js在處理完5后,才檢查這個(gè)Job,處理過程同5,Node.js又開始輪新一輪Tick(第三輪),本輸出發(fā)生在第三輪Tick時(shí)');
    process.nextTick(()=>{console.log('8: 第三輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù),因此第三輪Tick結(jié)束時(shí)立即執(zhí)行輸出')})},0) //FIXME nextTic再加入nextTick呢?本輪,還是下一輪Tick執(zhí)行

setImmediate(()=>{
    setTimeout(()=>{console.log('xx: 我在可能在13前或16后輸出,根據(jù)Node.js的處理速度,在10毫秒以內(nèi)則輸出在13前,大于10毫秒則輸出在16后,因?yàn)檫@個(gè)setTimeout排在MessageQueue最后!')},0)
    console.log('9: 第一輪Tick時(shí),第一個(gè)放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)。Node.js經(jīng)過以上三輪次Tick處理后,發(fā)現(xiàn)執(zhí)行棧為空,且MessageQueue沒有滿足條件的Job需要處理,準(zhǔn)備開始下一輪EventLoop處理。setImmediate的執(zhí)行時(shí)機(jī)就在下一輪EventLoop開始前,我又是第一個(gè),Node.js會(huì)把回調(diào)函數(shù)放回執(zhí)行棧,執(zhí)行棧非空,Node.js開始新一輪Tick(第四輪),本輸出發(fā)生在第四輪Tick時(shí)');
    process.nextTick(()=>{console.log('10: 第四輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù),因此第四輪Tick結(jié)束時(shí)立即執(zhí)行輸出')}) 
})

setImmediate(()=>{
    console.log('11: 第一輪Tick時(shí),第二個(gè)放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)。執(zhí)行完9,Node.js又開啟一輪Tick(第五輪)處理這個(gè)setImmediate。本輸出發(fā)生在第五輪Tick時(shí)')
    process.nextTick(()=>{console.log('12: 第五輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù),因此第五輪Tick結(jié)束時(shí)立即執(zhí)行輸出')}) 
})

setTimeout(()=>{console.log('xx: 第一輪Tick時(shí),第六個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù),輸出順序飄忽,但肯定都在nextTick之后。飄忽位置,取決于Node.js執(zhí)行到第XX輪Tick的耗時(shí)是否達(dá)到了2毫秒,如果達(dá)到,將在當(dāng)前輪Tick結(jié)束時(shí)得到執(zhí)行。例如:第三輪Tick結(jié)束時(shí),Node.js的處理耗時(shí)已經(jīng)2毫秒,則我會(huì)輸出在8后(Node.js開始新一個(gè)Tick執(zhí)行我)')},2) 

process.nextTick(()=>{ console.log('4: 第一輪Tick時(shí),第二個(gè)放到nextTick階段的回調(diào)函數(shù)。執(zhí)行棧已空時(shí)執(zhí)行,在nextTick中排序第二。第一輪Tick正式結(jié)束!')})

執(zhí)行命令 node s3.js 看具體結(jié)果(根據(jù)機(jī)器性能會(huì)稍有不同)

$ node s3.js 
0: 啟動(dòng)Node.js,開始了第一輪EventLoop,開始了第一輪Tick
1: 第一輪Tick時(shí),第一個(gè)非阻塞函數(shù)(current function)
2: 第一輪Tick時(shí),第二個(gè)非阻塞函數(shù)(current function)
3: 第一輪Tick時(shí),第一個(gè)放到nextTick階段的回調(diào)函數(shù)。執(zhí)行棧已空時(shí)執(zhí)行,在nextTick中排序第一
4: 第一輪Tick時(shí),第二個(gè)放到nextTick階段的回調(diào)函數(shù)。執(zhí)行棧已空時(shí)執(zhí)行,在nextTick中排序第二。第一輪Tick正式結(jié)束!
5: 第一輪Tick時(shí),第四個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 雖然是第四個(gè)放入MessageQueue的setTimeout,但延時(shí)0毫秒,第一輪Tick結(jié)束時(shí)Node.js檢查已滿足觸發(fā)條件,將對(duì)應(yīng)的回調(diào)函數(shù)放回執(zhí)行棧,因調(diào)用棧非空,Node.js開始了新一輪Tick(第二輪),本輸出發(fā)生在在第二輪Tick時(shí)
6: 第二輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù)。因?yàn)镹ode.js逐個(gè)檢查和執(zhí)行MessageQueue中的Job(處理過程見5),因此第二輪Tick結(jié)束時(shí)立即執(zhí)行輸出
7: 第一輪Tick時(shí),第五個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù),雖然也是延時(shí)0毫秒,但是按MessageQueu的先進(jìn)先出原則,Node.js在處理完5后,才檢查這個(gè)Job,處理過程同5,Node.js又開始輪新一輪Tick(第三輪),本輸出發(fā)生在第三輪Tick時(shí)
8: 第三輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù),因此第三輪Tick結(jié)束時(shí)立即執(zhí)行輸出
9: 第一輪Tick時(shí),第一個(gè)放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)。Node.js經(jīng)過以上三輪次Tick處理后,發(fā)現(xiàn)執(zhí)行棧為空,且MessageQueue沒有滿足條件的Job需要處理,準(zhǔn)備開始下一輪EventLoop處理。setImmediate的執(zhí)行時(shí)機(jī)就在下一輪EventLoop開始前,我又是第一個(gè),Node.js會(huì)把回調(diào)函數(shù)放回執(zhí)行棧,執(zhí)行棧非空,Node.js開始新一輪Tick(第四輪),本輸出發(fā)生在第四輪Tick時(shí)
10: 第四輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù),因此第四輪Tick結(jié)束時(shí)立即執(zhí)行輸出
11: 第一輪Tick時(shí),第二個(gè)放在Next new EventLoop start之前的setImmediate回調(diào)函數(shù)。執(zhí)行完9,Node.js又開啟一輪Tick(第五輪)處理這個(gè)setImmediate。本輸出發(fā)生在第五輪Tick時(shí)
12: 第五輪Tick時(shí),加入nextTick階段的回調(diào)函數(shù),因此第五輪Tick結(jié)束時(shí)立即執(zhí)行輸出
xx: 第一輪Tick時(shí),第六個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù),輸出順序飄忽,但肯定都在nextTick之后。飄忽位置,取決于Node.js執(zhí)行到第XX輪Tick的耗時(shí)是否達(dá)到了2毫秒,如果達(dá)到,將在當(dāng)前輪Tick結(jié)束時(shí)得到執(zhí)行。例如:第三輪Tick結(jié)束時(shí),Node.js的處理耗時(shí)已經(jīng)2毫秒,則我會(huì)輸出在8后(Node.js開始新一個(gè)Tick執(zhí)行我)
xx: 我在可能在13前或16后輸出,根據(jù)Node.js的處理速度,在10毫秒以內(nèi)則輸出在13前,大于10毫秒則輸出在16后,因?yàn)檫@個(gè)setTimeout排在MessageQueue最后!
13: 第一輪Tick時(shí),第一個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 延時(shí)10毫秒。終于到10毫秒了,Node.js新開一輪Tick執(zhí)行我
14: 第一輪Tick時(shí),第二個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 延時(shí)10毫秒。終于到10毫秒了,但是在MessageQueue中我排在13后。
15: 第一輪Tick時(shí),第三個(gè)放入MessageQueue的setTimeout回調(diào)函數(shù) 延時(shí)10毫秒。終于到10毫秒了,但是在MessageQueue中我排在14后
16: 輸出15步的Tick輪次時(shí)加入nextTick,該輪次結(jié)束時(shí),輸出了我。
17: 沒有任何Blocking任務(wù),Node.js結(jié)束EventLoop,退出Node.js

看完代碼運(yùn)行結(jié)果,集中精力看下圖中的「Event Loop」,體會(huì)一下EventLoop,一輪完整的EventLoop周期是怎么樣的!

image.png

Node.js方案的優(yōu)點(diǎn)

適合高并發(fā)場(chǎng)景!

Node 公開宣稱的目標(biāo)是 “旨在提供一種簡(jiǎn)單的構(gòu)建可伸縮網(wǎng)絡(luò)程序的方法”。我們來看一個(gè)簡(jiǎn)單的例子,在 Java和 PHP 這類語(yǔ)言中,每個(gè)連接都會(huì)生成一個(gè)新線程,每個(gè)新線程可能需要 2 MB 的配套內(nèi)存。在一個(gè)擁有 8 GB RAM 的系統(tǒng)上,理論上最大的并發(fā)連接數(shù)量是 4,000 個(gè)用戶。隨著您的客戶群的增長(zhǎng),如果希望您的 Web 應(yīng)用程序支持更多用戶,那么,您必須添加更多服務(wù)器。所以在傳統(tǒng)的后臺(tái)開發(fā)中,整個(gè) Web 應(yīng)用程序架構(gòu)(包括流量、處理器速度和內(nèi)存速度)中的瓶頸是:服務(wù)器能夠處理的并發(fā)連接的最大數(shù)量。這個(gè)不同的架構(gòu)承載的并發(fā)數(shù)量是不一致的。

而Node的出現(xiàn)就是為了解決這個(gè)問題:更改連接到服務(wù)器的方式。

在Node 聲稱它不允許使用鎖,它不會(huì)直接阻塞 I/O 調(diào)用。Node在每個(gè)連接發(fā)射一個(gè)在 Node 引擎的進(jìn)程中運(yùn)行的事件,而不是為每個(gè)連接生成一個(gè)新的 OS 線程(并為其分配一些配套內(nèi)存)。

Node.js方案的缺點(diǎn)

不適合CPU密集型處理!

如上所述,nodejs的機(jī)制是單線程,這個(gè)線程里面,有一個(gè)事件循環(huán)機(jī)制,處理所有的請(qǐng)求。在事件處理過程中,它會(huì)智能地將一些涉及到IO、網(wǎng)絡(luò)通信等耗時(shí)比較長(zhǎng)的操作,交由worker threads去執(zhí)行,執(zhí)行完了再回調(diào),這就是所謂的異步IO非阻塞。但是,那些非IO操作,只用CPU計(jì)算的操作,它就自己扛了,比如算什么斐波那契數(shù)列之類。它是單線程,這些自己扛的任務(wù)要一個(gè)接著一個(gè)地完成,前面那個(gè)沒完成,后面的只能干等。

因此,對(duì)CPU要求比較高的CPU密集型任務(wù)多的話,就有可能會(huì)造成號(hào)稱高性能,適合高并發(fā)的node.js服務(wù)器反應(yīng)緩慢。

相對(duì)而已CPU密集型的場(chǎng)景可以選用Apache——Apache具有多線程高并發(fā)共享內(nèi)存地址空間的特性,那就意味著如果服務(wù)器足夠強(qiáng)大,處理器足夠高核,Apache的運(yùn)作將會(huì)非常良好,所以適用于(并發(fā))異步處理相對(duì)較少,后臺(tái)計(jì)算量大,后臺(tái)業(yè)務(wù)邏輯復(fù)雜的應(yīng)用程序。

數(shù)據(jù)密集型:Data-Intensive applications,數(shù)據(jù)是其主要挑戰(zhàn)(數(shù)據(jù)量,數(shù)據(jù)復(fù)雜度,數(shù)據(jù)變化速度),與之相對(duì)的是計(jì)算密集型,即處理器速度是其瓶頸?,F(xiàn)今很多數(shù)據(jù)都是數(shù)據(jù)密集型的,而非計(jì)算密集型,CPU很少成為瓶頸。數(shù)據(jù)密集型應(yīng)用

適用場(chǎng)景舉例

既然NodeJS處理并發(fā)的能力強(qiáng),但處理計(jì)算和邏輯的能力反而很弱,因此,如果我們把復(fù)雜的邏輯運(yùn)算都搬到前端(客戶端)完成,而NodeJS只需要提供異步I/O,這樣就可以實(shí)現(xiàn)對(duì)高并發(fā)的高性能處理。

這樣的場(chǎng)景有很多,比如:

1、RESTful API

這是適合 Node 的理想情況,因?yàn)槟梢詷?gòu)建它來處理數(shù)萬條連接。它仍然不需要大量邏輯;它本質(zhì)上只是從某個(gè)數(shù)據(jù)庫(kù)中查找一些值并將它們組成一個(gè)響應(yīng)。由于響應(yīng)是少量文本,入站請(qǐng)求也是少量的文本,因此流量不高,一臺(tái)機(jī)器甚至也可以處理最繁忙的公司的 API 需求。完成數(shù)據(jù)型應(yīng)用中對(duì)數(shù)據(jù)的獲取功能。

2、實(shí)時(shí)程序

比如聊天服務(wù)

聊天應(yīng)用程序是最能體現(xiàn) Node.js 優(yōu)點(diǎn)的例子:輕量級(jí)、高流量并且能良好的應(yīng)對(duì)跨平臺(tái)設(shè)備上運(yùn)行密集型數(shù)據(jù)(雖然計(jì)算能力低)。同時(shí),聊天也是一個(gè)非常值得學(xué)習(xí)的用例,因?yàn)樗芎?jiǎn)單,并且涵蓋了目前為止一個(gè)典型的 Node.js 會(huì)用到的大部分解決方案。

3、單頁(yè)APP

客戶端邏輯強(qiáng)大的單頁(yè)APP,比如說:本地化的在線音樂應(yīng)用,本地化的在線搜索應(yīng)用,本地化的在線APP等。
ajax很多?,F(xiàn)在單頁(yè)的機(jī)制似乎很流行,比如phonegap做出來的APP,一個(gè)頁(yè)面包打天下的例子比比皆是。

總而言之,NodeJS適合運(yùn)用在高并發(fā)、I/O密集、少量業(yè)務(wù)邏輯(只有一個(gè)線程)的場(chǎng)景;

參考資料

參考資料:

  • 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ū)動(dòng)和非阻塞I/O

  • Nodejs的運(yùn)行原理-科普篇

    Node是一個(gè)服務(wù)器端JavaScript解釋器,用于方便地搭建響應(yīng)速度快、易于擴(kuò)展的網(wǎng)絡(luò)應(yīng)用。Node使用事件驅(qū)動(dòng),非阻塞I/O 模型而得以輕量和高效,非常適合在分布式設(shè)備上運(yùn)行數(shù)據(jù)密集型的實(shí)時(shí)應(yīng)用。Node是一個(gè)可以讓JavaScript運(yùn)行在瀏覽器之外的平臺(tái)。它實(shí)現(xiàn)了諸如文件系統(tǒng)、模塊、包、操作系統(tǒng) API、網(wǎng)絡(luò)通信等Core JavaScript沒有或者不完善的功能。歷史上將JavaScript移植到瀏覽器外的計(jì)劃不止一個(gè),但Node.js 是最出色的一個(gè)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容