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

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

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

從頭聊起

一個常見web應(yīng)用會做哪些事情

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

read操作基本步驟

首先看下一個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請求,等待數(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ù)

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

幾種I/O模型

阻塞式I/O

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


非阻塞式I/O

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


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

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


信號驅(qū)動I/O

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


異步I/O

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


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

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

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

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


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

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

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

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

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

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

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

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

當(dāng)前名稱:Node高并發(fā)的原理是什么
鏈接URL:http://muchs.cn/article16/pdggdg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)、網(wǎng)站建設(shè)、微信小程序、網(wǎng)頁設(shè)計公司、網(wǎng)站設(shè)計App設(shè)計

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)