Node.js自誕生之日起就以起高并發(fā)得名嗜价,其中的核心便是異步I/O了艇抠。那么到底什么是異步I/O?和傳統(tǒng)的同步I/O比又有什么優(yōu)點(diǎn)炭剪?讓我們從簡單的概念講起吧练链。
同步I/O
在操作系統(tǒng)中我們都學(xué)過,線程執(zhí)行在遇到I/O操作的時候會被阻塞奴拦。然后CPU轉(zhuǎn)而去處理該I/O操作媒鼓,得出結(jié)果后再返回該線程繼續(xù)執(zhí)行下去。這就是傳統(tǒng)意義上得同步I/O错妖,也是在編程中非常直觀的方式绿鸣。假設(shè)我們有一個名為"test.txt"的文件,下面讓我們寫一個讀取文件的小例子:
// fs是node的file system
var fs = require('fs');
// sync指明同步I/O方式
var data = fs.readFileSync('test.txt', 'utf-8');
console.log(data);
console.log('end of program.');
可以預(yù)見暂氯,該程序先讀取test文件的內(nèi)容并顯示在終端上潮模,然后打印一行end of program作為程序的結(jié)束。一般程序都是這么線性執(zhí)行的痴施,理解起來也非常容易擎厢。這么執(zhí)行有什么問題?
問題
這樣的線程執(zhí)行方式在一般情景下是完全沒有問題的辣吃,但我們將場景切換到互聯(lián)網(wǎng)上來看动遭。傳統(tǒng)服務(wù)器的處理方式是為每個請求開啟一個線程,在遇到I/O請求的時候就按上面說的同步方式來坐阻塞處理神得。但每個CPU能承受的線程數(shù)是有限制的厘惦,于是達(dá)到限制的時候就必須添加新的CPU。何況開啟線程是非常消耗資源的哩簿,訪問量教大的互聯(lián)網(wǎng)應(yīng)用宵蕉,服務(wù)器擴(kuò)展的速度可能會遠(yuǎn)遠(yuǎn)超過你的想象。
策略
那么有其他的解決方法么节榜?從上面的模型上來開羡玛,瓶頸主要是在阻塞上。于是異步I/O的概念就此誕生了宗苍。與同步想法稼稿,異步方式在遇到I/O操作時不阻塞線程亿遂,而是將I/O請求發(fā)給后臺執(zhí)行,然后線程繼續(xù)執(zhí)行下去渺杉。直到I/O操作完成后再通知線程來處理結(jié)果。于是線程的利用率就很高了挪钓,單個線程就能處理大量的請求是越。下面來看看異步方式I/O到底是什么樣的:
var fs = require('fs');
// node默認(rèn)是異步處理凡事
fs.readFile('test.txt', 'utf-8', function(err, data) {
// 讀取出錯則打印出錯信息,否則打印文件內(nèi)容
if (err) {
console.log(err);
} else {
console.log(data);
}
});
console.log('end of program.');
這個程序的執(zhí)行結(jié)果又是什么碌上?這次不再是先打印文件內(nèi)容了倚评,而是先打印了end of program,然后才是文件內(nèi)容馏予。就像上面提到的天梧,但程序執(zhí)行到讀取文件這個I/O操作的時候,不會再阻塞并等待了霞丧。去讀操作被交給后臺去處理呢岗,而線程繼續(xù)執(zhí)行下去,也就是執(zhí)行后面的end of program這條指令蛹尝。文件讀取完成后再打印文件的內(nèi)容后豫。
缺點(diǎn)
看起來好像異步方式是個非常完美的解決方案。別急著下結(jié)論突那,任何事情都有好的一面和壞的一面挫酿,沒有任何方案是絕對完美的。異步方式的缺點(diǎn)愕难,就在于其非線性的執(zhí)行方式帶給編寫程序的困難早龟。從上面的例子可以看到,異步程序的執(zhí)行結(jié)果與傳統(tǒng)的程序有很大區(qū)別猫缭。這就說明了編寫異步程序比較困難葱弟,需要改變傳統(tǒng)的程序設(shè)計(jì)思想。別低估了這個困難饵骨,傳統(tǒng)的力量是遠(yuǎn)遠(yuǎn)超出人的想象的翘悉!
結(jié)語
傳統(tǒng)的編程方式在互聯(lián)網(wǎng)不斷擴(kuò)張之后遇到了不少挑戰(zhàn)。并發(fā)處理居触,可擴(kuò)展性和降低成本也越來越受到重視(可以參考LINKIN招聘數(shù)據(jù))妖混。異步I/O無疑是一種(但非唯一)有效的解決方式。
稍后我們在一起看看采用異步I/O方式的NODE到底是如何利用異步模型來處理大規(guī)模并發(fā)的轮洋。到底是什么神奇的力量能讓服務(wù)器數(shù)量從三十臺降到三臺制市?