真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Node高并發(fā)的原理是什么

這篇文章主要介紹“Node高并發(fā)的原理是什么”的相關(guān)知識(shí),小編通過(guò)實(shí)際案例向大家展示操作過(guò)程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“Node高并發(fā)的原理是什么”文章能幫助大家解決問(wèn)題。

成都創(chuàng)新互聯(lián)成都網(wǎng)站建設(shè)定制網(wǎng)站開(kāi)發(fā),是成都網(wǎng)站設(shè)計(jì)公司,為活動(dòng)板房提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計(jì)服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計(jì)、前端HTML5制作、后臺(tái)程序開(kāi)發(fā)等。成都網(wǎng)站維護(hù)熱線:028-86922220

從頭聊起

一個(gè)常見(jiàn)web應(yīng)用會(huì)做哪些事情

  • 運(yùn)算(執(zhí)行業(yè)務(wù)邏輯、數(shù)學(xué)運(yùn)算、函數(shù)調(diào)用等。主要工作在CPU進(jìn)行)

  • I/O(如讀寫(xiě)文件、讀寫(xiě)數(shù)據(jù)庫(kù)、讀寫(xiě)網(wǎng)絡(luò)請(qǐng)求等。主要工作在各種I/O設(shè)備,如磁盤、網(wǎng)卡等)

一個(gè)典型的傳統(tǒng)web應(yīng)用實(shí)現(xiàn)

  • 多進(jìn)程,一個(gè)請(qǐng)求fork一個(gè)(子)進(jìn)程 + 阻塞I/O(即blocking I/O或BIO)

  • 多線程,一個(gè)請(qǐng)求創(chuàng)建一個(gè)線程 + 阻塞I/O


多進(jìn)程web應(yīng)用示例偽代碼

listenFd = new Socket(); // 創(chuàng)建監(jiān)聽(tīng)socket
Bind(listenFd, 80); // 綁定端口
Listen(listenFd);   // 開(kāi)始監(jiān)聽(tīng)

for ( ; ; ) {
    // 接收客戶端請(qǐng)求,通過(guò)新的socket建立連接
    connFd = Accept(listenFd);
    // fork子進(jìn)程
    if ((pid = Fork()) === 0) {
        // 子進(jìn)程中
        // BIO讀取網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),阻塞,發(fā)生進(jìn)程調(diào)度
        request = connFd.read();
        // BIO讀取本地文件,阻塞,發(fā)生進(jìn)程調(diào)度
        content = ReadFile('test.txt');
        // 將文件內(nèi)容寫(xiě)入響應(yīng)
        Response.write(content);
    }
}

多線程應(yīng)用實(shí)際上和多進(jìn)程類似,只不過(guò)將一個(gè)請(qǐng)求分配一個(gè)進(jìn)程換成了一個(gè)請(qǐng)求分配一個(gè)線程。線程對(duì)比進(jìn)程更輕量,在系統(tǒng)資源占用上更少,上下文切換(ps:所謂上下文切換,稍微解釋一下:?jiǎn)魏诵腃PU的情況下同一時(shí)間只能執(zhí)行一個(gè)進(jìn)程或線程中的任務(wù),而為了宏觀上的并行,則需要在多個(gè)進(jìn)程或線程之間按時(shí)間片來(lái)回切換以保證各進(jìn)、線程都有機(jī)會(huì)被執(zhí)行)的開(kāi)銷也更??;同時(shí)線程間更容易共享內(nèi)存,便于開(kāi)發(fā)

上文中提到了web應(yīng)用的兩個(gè)核心要點(diǎn),一個(gè)是進(jìn)(線)程模型,一個(gè)是I/O模型。那阻塞I/O到底是什么?又有哪些其他的I/O模型呢?別著急,首先我們看一下什么是阻塞

什么是阻塞?什么是阻塞I/O?

簡(jiǎn)而言之,阻塞是指函數(shù)調(diào)用返回之前,當(dāng)前進(jìn)(線)程會(huì)被掛起,進(jìn)入等待狀態(tài),在這個(gè)狀態(tài)下,當(dāng)前進(jìn)(線)程暫停運(yùn)行,引起CPU的進(jìn)(線)程調(diào)度。函數(shù)只有在內(nèi)部工作全部執(zhí)行完成后才會(huì)返回給調(diào)用者

所以阻塞I/O是,應(yīng)用程序通過(guò)API調(diào)用I/O操作后,當(dāng)前進(jìn)(線)程將會(huì)進(jìn)入等待狀態(tài),代碼無(wú)法繼續(xù)往下執(zhí)行,這時(shí)CPU可以進(jìn)行進(jìn)(線)程調(diào)度,即切換到其他可執(zhí)行的進(jìn)(線)程繼續(xù)執(zhí)行,當(dāng)前進(jìn)(線)程在底層I/O請(qǐng)求處理完后才會(huì)返回并可以繼續(xù)執(zhí)行

多進(jìn)(線)程 + 阻塞I/O模型有什么問(wèn)題?

在了解了什么是阻塞和阻塞I/O后,我們來(lái)分析一下傳統(tǒng)web應(yīng)用多進(jìn)(線)程 + 阻塞I/O模型有什么弊端。

因?yàn)橐粋€(gè)請(qǐng)求需要分配一個(gè)進(jìn)(線)程,這樣的系統(tǒng)在并發(fā)量大時(shí)需要維護(hù)大量進(jìn)(線)程,且需要進(jìn)行大量的上下文切換,這都需要大量的CPU、內(nèi)存等系統(tǒng)資源支撐,所以在高并發(fā)請(qǐng)求進(jìn)來(lái)時(shí)CPU和內(nèi)存開(kāi)銷會(huì)急劇上升,可能會(huì)迅速拖垮整個(gè)系統(tǒng)導(dǎo)致服務(wù)不可用

nodejs應(yīng)用實(shí)現(xiàn)

接下來(lái)我們看看nodejs應(yīng)用是如何實(shí)現(xiàn)的。

  • 事件驅(qū)動(dòng),單線程(主線程)

  • 非阻塞I/O 在官網(wǎng)上可以看到,nodejs最主要的兩大特點(diǎn),一個(gè)是單線程事件驅(qū)動(dòng),一個(gè)是“非阻塞”I/O模型。單線程 + 事件驅(qū)動(dòng)比較好理解,前端同學(xué)應(yīng)該都很熟悉js的單線程和事件循環(huán)這套機(jī)制了,那我們主要來(lái)研究一下這個(gè)“非阻塞I/O”是怎么一回事。首先來(lái)看一段nodejs服務(wù)端應(yīng)用常見(jiàn)的代碼,

const net = require('net');
const server = net.createServer();
const fs = require('fs');

server.listen(80);  // 監(jiān)聽(tīng)端口
// 監(jiān)聽(tīng)事件建立連接
server.on('connection', (socket) => {
    // 監(jiān)聽(tīng)事件讀取請(qǐng)求數(shù)據(jù)
    socket.on('data', (data) => {
    // 異步讀取本地文件
    fs.readFile('test.txt', (err, data) => {
            // 將讀取的內(nèi)容寫(xiě)入響應(yīng)
            socket.write(data);
            socket.end();
        })
    });
});

可以看到在nodejs中,我們可以以異步的方式去進(jìn)行I/O操作,通過(guò)API調(diào)用I/O操作后會(huì)馬上返回,緊接著就可以繼續(xù)執(zhí)行其他代碼邏輯,那為什么nodejs中的I/O是“非阻塞”的呢?回答這個(gè)問(wèn)題之前我們?cè)僮鲆恍?zhǔn)備工作,參考nodejs進(jìn)階視頻講解:進(jìn)入學(xué)習(xí)

read操作基本步驟

首先看下一個(gè)read操作需要經(jīng)歷哪些步驟

  • 用戶程序調(diào)用I/O操作API,內(nèi)部發(fā)出系統(tǒng)調(diào)用,進(jìn)程從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài)

  • 系統(tǒng)發(fā)出I/O請(qǐng)求,等待數(shù)據(jù)準(zhǔn)備好(如網(wǎng)絡(luò)I/O,等待數(shù)據(jù)從網(wǎng)絡(luò)中到達(dá)socket;等待系統(tǒng)從磁盤上讀取數(shù)據(jù)等)

  • 數(shù)據(jù)準(zhǔn)備好后,復(fù)制到內(nèi)核緩沖區(qū)

  • 從內(nèi)核空間復(fù)制到用戶空間,用戶程序拿到數(shù)據(jù)

接下來(lái)我們看一下操作系統(tǒng)中有哪些I/O模型

幾種I/O模型

阻塞式I/O

Node高并發(fā)的原理是什么


非阻塞式I/O

Node高并發(fā)的原理是什么


I/O多路復(fù)用(進(jìn)程可同時(shí)監(jiān)聽(tīng)多個(gè)I/O設(shè)備就緒)

Node高并發(fā)的原理是什么


信號(hào)驅(qū)動(dòng)I/O

Node高并發(fā)的原理是什么


異步I/O

Node高并發(fā)的原理是什么


那么nodejs里到底使用了哪種I/O模型呢?是上圖中的“非阻塞I/O”嗎?別著急,先接著往下看,我們來(lái)了解下nodejs的體系結(jié)構(gòu)

nodejs體系結(jié)構(gòu),線程、I/O模型分析

Node高并發(fā)的原理是什么

最上面一層是就是我們編寫(xiě)nodejs應(yīng)用代碼時(shí)可以使用的API庫(kù),下面一層則是用來(lái)打通nodejs和它所依賴的底層庫(kù)的一個(gè)中間層,比如實(shí)現(xiàn)讓js代碼可以調(diào)用底層的c代碼庫(kù)。來(lái)到最下面一層,可以看到前端同學(xué)熟悉的V8,還有其他一些底層依賴。注意,這里有一個(gè)叫l(wèi)ibuv的庫(kù),它是干什么的呢?從圖中也能看出,libuv幫助nodejs實(shí)現(xiàn)了底層的線程池、異步I/O等功能。libuv實(shí)際上是一個(gè)跨平臺(tái)的c語(yǔ)言庫(kù),它在windows、linux等不同平臺(tái)下會(huì)調(diào)用不同的實(shí)現(xiàn)。我這里主要分析linux下libuv的實(shí)現(xiàn),因?yàn)槲覀兊膽?yīng)用大部分時(shí)候還是運(yùn)行在linux環(huán)境下的,且平臺(tái)間的差異性并不會(huì)影響我們對(duì)nodejs原理的分析和理解。好了,對(duì)于nodejs在linux下的I/O模型來(lái)說(shuō),libuv實(shí)際上提供了兩種不同場(chǎng)景下的不同實(shí)現(xiàn),處理網(wǎng)絡(luò)I/O主要由epoll函數(shù)實(shí)現(xiàn)(其實(shí)就是I/O多路復(fù)用,在前面的圖中使用的是select函數(shù)來(lái)實(shí)現(xiàn)I/O多路復(fù)用,而epoll可以理解為select函數(shù)的升級(jí)版,這個(gè)暫時(shí)不做具體分析),而處理文件I/O則由多線程(線程池) + 阻塞I/O模擬異步I/O實(shí)現(xiàn)


下面是一段我寫(xiě)的nodejs底層實(shí)現(xiàn)的偽代碼幫助大家理解

listenFd = new Socket();    // 創(chuàng)建監(jiān)聽(tīng)socket
Bind(listenFd, 80); // 綁定端口
Listen(listenFd);   // 開(kāi)始監(jiān)聽(tīng)

for ( ; ; ) {
    // 阻塞在epoll函數(shù)上,等待網(wǎng)絡(luò)數(shù)據(jù)準(zhǔn)備好
    // epoll可同時(shí)監(jiān)聽(tīng)listenFd以及多個(gè)客戶端連接上是否有數(shù)據(jù)準(zhǔn)備就緒
    // clients表示當(dāng)前所有客戶端連接,curFd表示epoll函數(shù)最終拿到的一個(gè)就緒的連接
    curFd = Epoll(listenFd, clients);

    if (curFd === listenFd) {
        // 監(jiān)聽(tīng)套接字收到新的客戶端連接,創(chuàng)建套接字
        int connFd = Accept(listenFd);
        // 將新建的連接添加到epoll監(jiān)聽(tīng)的list
        clients.push(connFd);
    }

    else {
        // 某個(gè)客戶端連接數(shù)據(jù)就緒,讀取請(qǐng)求數(shù)據(jù)
        request = curFd.read();
        // 這里拿到請(qǐng)求數(shù)據(jù)后可以發(fā)出data事件進(jìn)入nodejs的事件循環(huán)
        ...
    }
}

// 讀取本地文件時(shí),libuv用多線程(線程池) + BIO模擬異步I/O
ThreadPool.run((callback) => {
    // 在線程里用BIO讀取文件
    String content = Read('text.txt');  
    // 發(fā)出事件調(diào)用nodejs提供的callback
});

通過(guò)I/O多路復(fù)用 + 多線程模擬的異步I/O配合事件循環(huán)機(jī)制,nodejs就實(shí)現(xiàn)了單線程處理并發(fā)請(qǐng)求并且不會(huì)阻塞。所以回到之前所說(shuō)的“非阻塞I/O”模型,實(shí)際上nodejs并沒(méi)有直接使用通常定義上的非阻塞I/O模型,而是I/O多路復(fù)用模型 + 多線程BIO。我認(rèn)為“非阻塞I/O”其實(shí)更多是對(duì)nodejs編程人員來(lái)說(shuō)的一種描述,從編碼方式和代碼執(zhí)行順序上來(lái)講,nodejs的I/O調(diào)用的確是“非阻塞”的。

關(guān)于“Node高并發(fā)的原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。


新聞標(biāo)題:Node高并發(fā)的原理是什么
當(dāng)前地址:http://weahome.cn/article/pdggdg.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部