可以做動(dòng)效的網(wǎng)站產(chǎn)品網(wǎng)絡(luò)營銷策劃方案
一、多路復(fù)用要解決的問題:
并發(fā)多客戶端連接,在多路復(fù)用之前的處理方案是同步阻塞網(wǎng)絡(luò)IO模型,這種模型的特點(diǎn)就是用一個(gè)進(jìn)程來處理一個(gè)網(wǎng)絡(luò)連接。優(yōu)點(diǎn)在于比較簡單,缺點(diǎn)在于性能較差,每個(gè)用戶請(qǐng)求到來都得占用一個(gè)進(jìn)程來處理,來一個(gè)請(qǐng)求就要分配一個(gè)進(jìn)程跟進(jìn)處理;最好是使用一個(gè)進(jìn)程處理多個(gè)連接請(qǐng)求
可以采用Linux提供的IO多路復(fù)用機(jī)制,這里的復(fù)用指的就是對(duì)進(jìn)程的復(fù)用
多路是指多個(gè)客戶端連接,指的是多條TCP連接,復(fù)用是指用一個(gè)進(jìn)程處理多條的連接,使用單進(jìn)程就能夠?qū)崿F(xiàn)同時(shí)處理多個(gè)客戶端的連接;實(shí)現(xiàn)了一個(gè)進(jìn)程處理大量的用戶連接。IO多路復(fù)用類似一個(gè)規(guī)范和接口,落地實(shí)現(xiàn);
二、Redis單線程是如何處理多并發(fā)連接:
Redis利用epoll實(shí)現(xiàn)IO多路復(fù)用,將連接信息和事件放到隊(duì)列中,一次放到文件事件分派器,事件分派器將事件分發(fā)給事件處理器
Redis 是跑在單線程中的,所有的操作都是按照順序線性執(zhí)行的,但是由于讀寫操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回,這會(huì)導(dǎo)致某一文件的IO阻塞,進(jìn)而導(dǎo)致整個(gè)進(jìn)程無法對(duì)其他客戶提供服務(wù),而IO多路復(fù)用就是為了解決這個(gè)問題而出現(xiàn);
所謂IO多路復(fù)用機(jī)制,就是通過一種機(jī)制可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或?qū)懢途w),能夠通知程序進(jìn)行相應(yīng)的讀寫操作。這種機(jī)制的使用需要select 、 poll、 epoll來配合。多個(gè)連接共用一個(gè)阻塞對(duì)象,應(yīng)用程序只需要在一個(gè)阻塞對(duì)象上等待,無需阻塞等待所有連接。當(dāng)某條連接有新的數(shù)據(jù)可以處理時(shí),操作系統(tǒng)通知應(yīng)用程序,線程從阻塞狀態(tài)返回,開始進(jìn)行業(yè)務(wù)處理。
Redis服務(wù)采用Reactor的方式來實(shí)現(xiàn)文件事件處理器(每一個(gè)網(wǎng)絡(luò)連接其實(shí)都對(duì)應(yīng)一個(gè)文件描述符);Redis基于Reactor模式開發(fā)了網(wǎng)絡(luò)事件處理器,這個(gè)處理器被稱為文件事件處理器。它的組成結(jié)構(gòu)為4部分:多個(gè)套接字、IO多路復(fù)用程序、文件事件分派器以及事件處理器,因?yàn)槲募录峙善麝?duì)列的消費(fèi)是單線程的,所以Redis才叫做單線程模型
三、Unix網(wǎng)絡(luò)編程中的IO模型:
同步:調(diào)用者要一直等待調(diào)用結(jié)果的通知后才能進(jìn)行后續(xù)的執(zhí)行;
異步:被調(diào)用方先返回應(yīng)答讓調(diào)用者先回去,然后再計(jì)算調(diào)用結(jié)果,計(jì)算完最終結(jié)果后再通知并返回給調(diào)用方;一般需要通過回調(diào)獲得結(jié)果
阻塞:調(diào)用方一直在等待而別的事情都不做
非阻塞:調(diào)用在發(fā)出后調(diào)用方先去忙別的事情,不會(huì)阻塞當(dāng)前進(jìn)程/線程,而會(huì)立即返回
(1).阻塞IO(Blocking IO,BIO):
當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用,kernel就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)(對(duì)于網(wǎng)絡(luò)IO來說,很多時(shí)候數(shù)據(jù)在一開始還沒有到達(dá);比如還沒有收到一個(gè)完整的UDP包。這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來)。這個(gè)過程需要等待,也就是說數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個(gè)過程的。
而在用戶進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞(當(dāng)然,是進(jìn)程自己選擇的阻塞)。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進(jìn)程才解除block的狀態(tài),重新運(yùn)行起來。所以,BIO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了。
可以利用多線程,只要連接了一個(gè)socket,操作系統(tǒng)分配一個(gè)線程來處理,這樣read()方法堵塞在每個(gè)具體線程上而不堵塞主線程,就能操作多個(gè)socket了,哪個(gè)線程中的socket有數(shù)據(jù),就讀哪個(gè)socket,各取所需,靈活統(tǒng)一。程序服務(wù)端只負(fù)責(zé)監(jiān)聽是否有客戶端連接,使用accept()阻塞;客戶端1連接服務(wù)端,就開辟一個(gè)線程(thread1)來執(zhí)行 read()方法,程序服務(wù)端繼續(xù)監(jiān)聽;客戶端2連接服務(wù)端,也開辟一個(gè)線程(thread2)來執(zhí)行read()方法,程序服務(wù)端繼續(xù)監(jiān)聽;任何一個(gè)線程上的socket有數(shù)據(jù)發(fā)送過來,read()就能立馬讀到,cpu就能進(jìn)行處理
多線程模型的缺點(diǎn)在于每來一個(gè)客戶端就要開辟一個(gè)線程,如果來1萬個(gè)客戶端,那就要開辟1萬個(gè)線程。在操作系統(tǒng)中用戶態(tài)不能直接開辟線程,需要調(diào)用內(nèi)核來創(chuàng)建的一個(gè)線程,這其中還涉及到用戶狀態(tài)的切換(上下文的切換),十分耗資源??梢酝ㄟ^使用線程池或者使用NIO(非阻塞IO)的方式解決
(2)非阻塞IO(No Blocking IO):
當(dāng)用戶進(jìn)程發(fā)出read操作時(shí),如果kernel中的數(shù)據(jù)還沒有準(zhǔn)備好,那么它并不會(huì)block用戶進(jìn)程,而是立刻返回一個(gè)erorr;從用戶進(jìn)程角度講,它發(fā)起一個(gè)read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。用戶進(jìn)程判斷結(jié)果是一個(gè)erorr時(shí),它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回。所以,NIO特點(diǎn)是用戶進(jìn)程需要不斷的主動(dòng)詢問內(nèi)核數(shù)據(jù)是否準(zhǔn)備好
在NIO模式中,一切都是非阻塞的;accept()方法是非阻塞的,如果沒有客戶端連接,就返回?zé)o連接標(biāo)識(shí);read()方法是非阻塞的,如果read()方法讀取不到數(shù)據(jù)就返回空閑中標(biāo)識(shí),如果讀取到數(shù)據(jù)時(shí)只阻塞read()方法讀數(shù)據(jù)的時(shí)間
在NIO模式中,只有一個(gè)線程:當(dāng)一個(gè)客戶端與服務(wù)端進(jìn)行連接,這個(gè)socket就會(huì)加入到一個(gè)數(shù)組中,隔一段時(shí)間遍歷一次,看這個(gè)socket的read()方法能否讀到數(shù)據(jù),這樣一個(gè)線程就能處理多個(gè)客戶端的連接和讀取
NIO成功的解決了BIO需要開啟多線程的問題,一個(gè)線程就能解決多個(gè)socket,不會(huì)阻塞在內(nèi)核的等待過程,每次發(fā)起的IO請(qǐng)求可以立即返回,不用阻塞等待,實(shí)時(shí)性較好;缺點(diǎn)在于輪詢將不斷地詢問內(nèi)核,這將占用大量地CPU時(shí)間,系統(tǒng)資源利用率較低。
(3).IO Multiplexing(IO多路復(fù)用):
IO Multipexing指的是在單個(gè)線程通過記錄跟蹤每一個(gè)sock地狀態(tài)來同時(shí)管理多個(gè)IO流,目的是盡量多的提高服務(wù)器的吞吐能力
IO multiplexing就是我們說的select,poll,epoll;有些技術(shù)書籍也稱這種IO方式為event driven IO事件驅(qū)動(dòng)IO。就是通過一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作??梢曰谝粋€(gè)阻塞對(duì)象并同時(shí)在多個(gè)描述符上等待就緒,而不是使用多個(gè)線程(每個(gè)文件描述符一個(gè)線程,每次new一個(gè)線程),這樣可以大大節(jié)省系統(tǒng)資源。所以,IO多路復(fù)用的特點(diǎn)是通過一種機(jī)制一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài),select,poll,epoll等所數(shù)就可以返回。
a.select函數(shù):
select其實(shí)就是把NIO中用戶態(tài)要遍歷的fd數(shù)組(我們的每一個(gè)socket鏈接,安裝進(jìn)ArrayList里面的那個(gè))拷貝到了內(nèi)核態(tài),讓內(nèi)核態(tài)來遍歷,因?yàn)橛脩魬B(tài)判斷socket是否有數(shù)據(jù)還是要調(diào)用內(nèi)核態(tài)的,所有拷貝到內(nèi)核態(tài)后,這樣遍歷判斷的時(shí)候就不用一直用戶態(tài)和內(nèi)核態(tài)頻繁切換
select函數(shù)存在一些缺點(diǎn),首先bitmap最大1024位,一個(gè)進(jìn)程最多只能處理1024個(gè)客戶端;其次&rset不可重用,每次socket有數(shù)據(jù)就相應(yīng)的位會(huì)被置位;另外文件描述符數(shù)組拷貝到了內(nèi)核態(tài)(只不過無系統(tǒng)調(diào)用切換上下文的開銷。(內(nèi)核層可優(yōu)化為異步事件通知)),仍然有開銷。select調(diào)用需要傳入fd數(shù)組,需要拷貝一份到內(nèi)核,高并發(fā)場(chǎng)景下這樣的拷貝消耗的資源是驚人的。(可優(yōu)化為不復(fù)制);最后select并沒有通知用戶態(tài)哪一個(gè)socket有數(shù)據(jù),仍然需要O(n)的遍歷。select僅僅返回可讀文件描述符的個(gè)數(shù),具體哪個(gè)可讀還是要用戶自己遍歷。(可優(yōu)化為只返回給用戶就緒的文件描述符,無需用戶做無效的遍歷)
select方式既做到了一個(gè)線程處理多個(gè)客戶端連接(文件描述符),又減少了系統(tǒng)調(diào)用的開銷(多個(gè)文件描述符只有一次 select 的系統(tǒng)調(diào)用 +N次就緒狀態(tài)的文件描述符的read系統(tǒng)調(diào)用)
b.poll函數(shù):
c.epoll函數(shù):
三種方法的對(duì)比:
(4).Reactor模式:
Reactor 模式,是指通過一個(gè)或多個(gè)輸入同時(shí)傳遞給服務(wù)處理器的服務(wù)請(qǐng)求的事件驅(qū)動(dòng)處理模式。服務(wù)端程序處理傳入多路請(qǐng)求,并將它們同步分派給請(qǐng)求對(duì)應(yīng)的處理線程,Reactor 模式也叫 Dispatcher 模式。即I/O多了復(fù)用統(tǒng)一監(jiān)聽事件,收到事件后分發(fā)(Dispatch 給某進(jìn)程),是編寫高性能網(wǎng)絡(luò)服務(wù)器的必備技術(shù);
Reactor 模式中有 2 個(gè)關(guān)鍵組成:
a.Reactor:Reactor 在一個(gè)單獨(dú)的線程中運(yùn)行,負(fù)責(zé)監(jiān)聽和分發(fā)事件,分發(fā)給適當(dāng)?shù)奶幚沓绦騺韺?duì)IO事件做出反應(yīng)。?
b.Handlers:處理程序執(zhí)行IO事件要完成的實(shí)際事件,類似于客戶想要與之交談的公司中的實(shí)際辦理人。Reactor通過調(diào)度適當(dāng)?shù)奶幚沓绦騺眄憫?yīng)I/O事件,處理程序執(zhí)行非阻塞操作。
四、五種IO模型總結(jié):
?