想成為大牛,不得不懂的五種Linux網(wǎng)絡(luò)IO模型
前言
你知道Netty為什么性能這么高嗎?你知道Redis為什么單線(xiàn)程如此之快嗎?這都和底層的網(wǎng)絡(luò)IO模型有關(guān)系,所以掌握網(wǎng)絡(luò)IO模型真的很重要,是一個(gè)基礎(chǔ),對(duì)你更好的理解其他應(yīng)用幫助非常大,今天我們就好好來(lái)聊聊Linux的5種網(wǎng)絡(luò)IO模型。
IO工作原理
我們的應(yīng)用大多數(shù)情況都是部署在linux系統(tǒng)中,linux系統(tǒng)也是一種應(yīng)用,它是基于計(jì)算機(jī)硬件的一種操作系統(tǒng)軟件。當(dāng)我們接收一次網(wǎng)絡(luò)傳輸,計(jì)算機(jī)硬件的網(wǎng)卡會(huì)從網(wǎng)絡(luò)中將讀到的字節(jié)流寫(xiě)到linux的buffer緩沖區(qū)內(nèi)存中,然后用戶(hù)空間會(huì)調(diào)用linux對(duì)外暴露的接口,將linux內(nèi)核空間buffer內(nèi)存中的數(shù)據(jù)拷貝到用戶(hù)空間的buffer區(qū)。這一次網(wǎng)絡(luò)讀取就是磁盤(pán)IO,同理從磁盤(pán)中讀取,也是遵循一樣的機(jī)制。
IO的性能瓶頸主要是下面兩個(gè)階段:
- 準(zhǔn)備階段,指數(shù)據(jù)從網(wǎng)絡(luò)網(wǎng)卡或本地存儲(chǔ)器讀取到內(nèi)核的過(guò)程
- 復(fù)制階段,指將內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝至用戶(hù)態(tài)的進(jìn)程緩沖區(qū)
所以,Linux系統(tǒng)中提供了五種IO模型來(lái)提高性能,它們分別為BIO、NIO、多路復(fù)用、信號(hào)驅(qū)動(dòng)、AIO,從性能上來(lái)說(shuō),它們屬于依次遞進(jìn)的關(guān)系,但越靠后的IO模型實(shí)現(xiàn)也越為復(fù)雜。
1. 阻塞IO模型BIO
當(dāng)用戶(hù)應(yīng)用線(xiàn)程調(diào)用linux操作系統(tǒng)的recvfrom?函數(shù)讀取數(shù)據(jù)的時(shí)候,如果內(nèi)核的buffer?內(nèi)存中沒(méi)有數(shù)據(jù),那么用戶(hù)線(xiàn)程會(huì)阻塞等待,直到內(nèi)核的buffer?內(nèi)存中有數(shù)據(jù)了,才去將內(nèi)核的buffer內(nèi)存中的數(shù)據(jù)拷貝到用戶(hù)應(yīng)用內(nèi)存中。
打比方理解:
比如你給女神發(fā)一條短信, 說(shuō)我來(lái)找你了, 然后就默默的一直等著女神下樓, 這個(gè)期間除了等待你不會(huì)做其他事情, 屬于備胎做法。也就是說(shuō)線(xiàn)程會(huì)一直阻塞等待內(nèi)核把數(shù)據(jù)準(zhǔn)備好,然后將數(shù)據(jù)copy到用戶(hù)空間。
優(yōu)點(diǎn):
- 開(kāi)發(fā)簡(jiǎn)單,容易入門(mén);
- 在阻塞等待期間,用戶(hù)線(xiàn)程掛起,在掛起期間不會(huì)占用CPU資源。
缺點(diǎn):
- 在BIO這種模型中,為了支持并發(fā)請(qǐng)求,通常會(huì)采用多線(xiàn)程的方式,并發(fā)過(guò)高時(shí)會(huì)導(dǎo)致創(chuàng)建大量線(xiàn)程,造成頻繁的上下文切換,甚至系統(tǒng)崩潰
在Java常用的Tomcat服務(wù)器中,Tomcat7.x版本以下默認(rèn)的IO類(lèi)型也是BIO,但是Tomcat中對(duì)BIO模型稍微進(jìn)行了優(yōu)化,通過(guò)線(xiàn)程池做了限制,所以避免出現(xiàn)并發(fā)過(guò)高而系統(tǒng)崩潰的情況。
2. 非阻塞IO模型NIO
當(dāng)用戶(hù)應(yīng)用線(xiàn)程調(diào)用linux操作系統(tǒng)的recvfrom?函數(shù)讀取數(shù)據(jù)的時(shí)候,如果內(nèi)核的buffer?內(nèi)存中沒(méi)有數(shù)據(jù),那么用戶(hù)線(xiàn)程會(huì)直接拿到結(jié)果(沒(méi)有數(shù)據(jù))不會(huì)阻塞等待,于是又會(huì)發(fā)起一次recvfrom函數(shù)調(diào)用,直到內(nèi)核的buffer內(nèi)存中有數(shù)據(jù)了,才去將內(nèi)核的buffer內(nèi)存中的數(shù)據(jù)拷貝到用戶(hù)應(yīng)用內(nèi)存中。
在非阻塞IO模型中,用戶(hù)線(xiàn)程需要不斷地詢(xún)問(wèn)內(nèi)核數(shù)據(jù)是否就緒,也就說(shuō)非阻塞IO不會(huì)交出CPU,而會(huì)一直占用CPU。
打比方理解:
比如你給女神發(fā)短信, 如果不回, 接著再發(fā), 一直發(fā)到女神下樓, 這個(gè)期間你除了發(fā)短信等待不會(huì)做其他事情, 屬于專(zhuān)一做法。同理,用戶(hù)線(xiàn)程無(wú)需等待內(nèi)核數(shù)據(jù)準(zhǔn)備結(jié)果,直接返回,然后通過(guò)輪詢(xún)?nèi)?wèn)結(jié)果,如果結(jié)果為準(zhǔn)備好,進(jìn)程把數(shù)據(jù)copy到用戶(hù)空間。
優(yōu)點(diǎn):
- 每次發(fā)起IO調(diào)用,在內(nèi)核等待數(shù)據(jù)的過(guò)程中可以立即返回,用戶(hù)線(xiàn)程不會(huì)阻塞。
缺點(diǎn):
- 多個(gè)線(xiàn)程不斷輪詢(xún)內(nèi)核是否有數(shù)據(jù),會(huì)占用大量CPU時(shí)間。
NIO相對(duì)來(lái)說(shuō)較為雞肋,因此目前大多數(shù)的NIO技術(shù)并非采用這種多線(xiàn)程的模型,而是基于單線(xiàn)程的多路復(fù)用模型實(shí)現(xiàn)的,Java中支持的NIO模型亦是如此。
3. 多路復(fù)用IO模型
前面提到NIO由于線(xiàn)程在不斷的輪詢(xún)查看數(shù)據(jù)是否準(zhǔn)備就緒,造成CPU開(kāi)銷(xiāo)較大。既然說(shuō)是由于大量無(wú)效的輪詢(xún)?cè)斐蒀PU占用過(guò)高,那么等內(nèi)核中的數(shù)據(jù)準(zhǔn)備好了之后,再去詢(xún)問(wèn)數(shù)據(jù)是否就緒是不是就可以了?答案是Yes。這就是我們多路復(fù)用IO模型的設(shè)計(jì)思想。
多路復(fù)用IO模型是基于文件描述符File Descriptor?實(shí)現(xiàn)的,文件描述符是打開(kāi)現(xiàn)存文件或新建文件時(shí),內(nèi)核會(huì)返回一個(gè)文件描述符。讀寫(xiě)文件也需要使用文件描述符來(lái)指定待讀寫(xiě)的文件。文件包含音頻文件,常規(guī)文件,硬件設(shè)備等等,也包括網(wǎng)絡(luò)套接字(Socket)。
IO多路復(fù)用就是利用Linux的內(nèi)核單線(xiàn)程去監(jiān)聽(tīng)多個(gè)文件描述符,并在某個(gè)文件描述符可讀、可寫(xiě)的時(shí)候接收到通知,避免無(wú)效的等待,充分利用CPU資源。
關(guān)于監(jiān)聽(tīng)的策略,在linux中提供了3種模式,也就是3個(gè)函數(shù),分別是 select、poll、epoll,如下圖所示。
- select?時(shí)間復(fù)雜度O(n),它僅僅知道了,有I/O事件發(fā)生了,卻并不知道是哪幾個(gè)流(可能有一個(gè),多個(gè),甚至全部),我們只能無(wú)差別輪詢(xún)所有流,找出能讀出數(shù)據(jù),或者寫(xiě)入數(shù)據(jù)的流,對(duì)他們進(jìn)行操作。所以select具有O(n)的無(wú)差別輪詢(xún)復(fù)雜度,同時(shí)處理的流越多,無(wú)差別輪詢(xún)時(shí)間就越長(zhǎng),而且最多支持的連接數(shù)量是1024個(gè)。
- poll?(翻譯:輪詢(xún))時(shí)間復(fù)雜度O(n),poll本質(zhì)上和select沒(méi)有區(qū)別,它將用戶(hù)傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢(xún)每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài), 但是它沒(méi)有最大連接數(shù)的限制,原因是它是基于鏈表來(lái)存儲(chǔ)的。
- epoll?時(shí)間復(fù)雜度O(1),epoll可以理解為event poll,不同于忙輪詢(xún)和無(wú)差別輪詢(xún),epoll會(huì)把哪個(gè)流發(fā)生了怎樣的I/O事件通知我們。所以我們說(shuō)epoll實(shí)際上是事件驅(qū)動(dòng)(每個(gè)事件關(guān)聯(lián)上fd)的,此時(shí)我們對(duì)這些流的操作都是有意義的。(復(fù)雜度降低到了O(1))。
打個(gè)比方理解:
IO多路復(fù)用相當(dāng)于找一個(gè)宿管大媽來(lái)幫你監(jiān)視下樓的女生, 這個(gè)期間你可以些其他的事情. 例如可以順便看看其他妹子,玩玩王者榮耀, 上個(gè)廁所等等。IO復(fù)用又包括 select、poll、epoll 模式。那么它們的區(qū)別是什么?
- select?大媽 每一個(gè)女生下樓, select大媽都不知道這個(gè)是不是你的女神, 她需要一個(gè)一個(gè)詢(xún)問(wèn), 并且select大媽能力還有限, 最多一次幫你監(jiān)視1024個(gè)妹子。
- poll大媽不限制盯著女生的數(shù)量, 只要是經(jīng)過(guò)宿舍樓門(mén)口的女生, 都會(huì)幫你去問(wèn)是不是你女神。
- epoll大媽不限制盯著女生的數(shù)量, 并且也不需要一個(gè)一個(gè)去問(wèn). 那么如何做呢? epoll大媽會(huì)為每個(gè)進(jìn)宿舍樓的女生臉上貼上一個(gè)大字條,上面寫(xiě)上女生自己的名字, 只要女生下樓了, epoll大媽就知道這個(gè)是不是你女神了, 然后大媽再通知你。
通知你之后,你需要到女生宿舍門(mén)口,把女神帶回自己宿色。
優(yōu)點(diǎn):
- 系統(tǒng)不必創(chuàng)建維護(hù)大量線(xiàn)程,只使用一個(gè)線(xiàn)程,一個(gè)選擇器即可同事處理成千上萬(wàn)個(gè)連接,大大減少系統(tǒng)開(kāi)銷(xiāo)。
缺點(diǎn):
- 本質(zhì)上,它還是同步的,數(shù)據(jù)準(zhǔn)備好后,拷貝階段依然是阻塞的。
如果處理的連接數(shù)不是很高的話(huà),使用select/epoll的web server?不一定比使用mutil-threading + blocking IO的web server?性能更好,可能延遲還更大。select/epoll 的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更好,而是在于性能更多的連接,比如Redis、Netty都是采用這種IO模型。
4. 信號(hào)驅(qū)動(dòng)IO模型
當(dāng)用戶(hù)應(yīng)用線(xiàn)程調(diào)用linux操作系統(tǒng)的sigaction函數(shù),直接返回,然后該線(xiàn)程去做其他事情了,當(dāng)有數(shù)據(jù)來(lái)了的時(shí)候,內(nèi)核空間會(huì)去遞交信號(hào)給用戶(hù)空間,此時(shí)用戶(hù)空間會(huì)調(diào)用recvfrom函數(shù)去將數(shù)據(jù)從內(nèi)核空間緩沖區(qū)拷貝到用戶(hù)空間緩沖區(qū),并處理數(shù)據(jù)。
打比方理解:
你給女神發(fā)短信,她下樓了主動(dòng)通知你,你這時(shí)候在宿舍門(mén)口等著把她待會(huì)自己宿舍。同比,進(jìn)程無(wú)需等待內(nèi)核數(shù)據(jù)準(zhǔn)備結(jié)果,直接返回,事件信號(hào)通知進(jìn)程結(jié)果,進(jìn)程把數(shù)據(jù)copy到用戶(hù)空間。
優(yōu)點(diǎn):
- 從一定意義上實(shí)現(xiàn)了異步,也就是數(shù)據(jù)的準(zhǔn)備階段是異步非阻塞執(zhí)行的
缺點(diǎn):
- 當(dāng)調(diào)用的線(xiàn)程過(guò)多,對(duì)應(yīng)的信號(hào)量會(huì)增多,SIGIO函數(shù)處理不及時(shí),會(huì)導(dǎo)致保存信號(hào)的隊(duì)列溢出
- 內(nèi)核空間與用戶(hù)空間頻繁的進(jìn)行信號(hào)量的交互,性能很差。
現(xiàn)實(shí)中這種模式用的不多,就不展開(kāi)闡述了??v觀上述的所有IO模型:BIO、NIO、多路復(fù)用、信號(hào)驅(qū)動(dòng),本質(zhì)上從內(nèi)核緩沖區(qū)拷貝數(shù)據(jù)到程序緩沖區(qū)的過(guò)程都是阻塞的,如果想要做到真正意義上的異步非阻塞IO,那么就牽扯到了異步IO模型。
5. 異步IO模型AIO
異步非阻塞模型,該模型是真正意義上的異步非阻塞式IO,數(shù)據(jù)準(zhǔn)備與復(fù)制階段都是異步非阻塞的。
在該模型中,首先用戶(hù)進(jìn)程中會(huì)創(chuàng)建一個(gè)Sigio?信號(hào)處理程序,然后會(huì)系統(tǒng)調(diào)用sigaction?信號(hào)處理函數(shù),緊接著內(nèi)核會(huì)直接讓用戶(hù)進(jìn)程中的線(xiàn)程返回,用戶(hù)進(jìn)程可在這期間干別的工作,當(dāng)內(nèi)核中的數(shù)據(jù)準(zhǔn)備好之后,內(nèi)核會(huì)生成一個(gè)Sigio?信號(hào),通知對(duì)應(yīng)的用戶(hù)進(jìn)程數(shù)據(jù)已準(zhǔn)備就緒,然后由用戶(hù)進(jìn)程在觸發(fā)一個(gè)recvfrom的系統(tǒng)調(diào)用,從內(nèi)核中將數(shù)據(jù)拷貝出來(lái)進(jìn)行處理。
打比方理解:
你給女神發(fā)短信,女神準(zhǔn)備好了并且主動(dòng)來(lái)到你宿舍通知你,你開(kāi)門(mén)。 同比,應(yīng)用進(jìn)程把IO請(qǐng)求傳給內(nèi)核后,完全由內(nèi)核去操作文件拷貝到用戶(hù)空間。內(nèi)核完成相關(guān)操作后,會(huì)發(fā)信號(hào)告訴應(yīng)用進(jìn)程本次IO已經(jīng)完成。
優(yōu)點(diǎn):
- 真正實(shí)現(xiàn)了異步非阻塞,吞吐量高
缺點(diǎn):
- 對(duì)內(nèi)核有要求,比如Linux系統(tǒng)中,異步IO在2.6才引入
總結(jié)
本文講解了Linux系統(tǒng)中5中IO模型,其中前面4種都屬于同步IO,因?yàn)閿?shù)據(jù)拷貝階段都是處于阻塞狀態(tài),只有異步IO模型才真正實(shí)現(xiàn)了異步非阻塞。
? ?