4.1 網(wǎng)絡(luò)IO模型以及Redis的實(shí)現(xiàn)

網(wǎng)絡(luò)IO模型通過內(nèi)核的不斷升級(jí),現(xiàn)在已經(jīng)有5種模式,這5種模式各有利弊。

五種IO模型

阻塞IO

監(jiān)聽一個(gè)fd,傳遞給內(nèi)核,然后阻塞等待,內(nèi)核發(fā)現(xiàn)此fd有事件產(chǎn)生時(shí)返回fd以及事件

非阻塞IO

監(jiān)聽一個(gè)fd,傳遞給內(nèi)核,然后不管fd是否有事件發(fā)生,直接返回處理其他事件,但會(huì)一直輪詢問內(nèi)核是否fd有事件發(fā)生.這樣用戶空間一直對系統(tǒng)調(diào)用,造成cpu資源浪費(fèi),減低效率

異步IO

當(dāng)前只有windows的iocp屬于異步io。異步io必須滿足2個(gè)條件:

  1. 內(nèi)核到用戶空間是無阻塞的。
  2. 硬件到內(nèi)核是無阻塞的
    而其他IO模型內(nèi)核空間拷貝數(shù)據(jù)到用戶態(tài)是阻塞的,只有iocp是系統(tǒng)內(nèi)核執(zhí)行完成后才告知用戶。
信號(hào)驅(qū)動(dòng)IO

監(jiān)聽一個(gè)fd,傳遞給內(nèi)核,非阻塞,后續(xù)內(nèi)核收到此fd事件變化,發(fā)送一個(gè)信號(hào)給用戶。

多路復(fù)用IO

前面的這些IO模型都是監(jiān)聽一個(gè)fd,如果系統(tǒng)有100萬個(gè)連接,那么則需要去實(shí)現(xiàn)100萬個(gè)線程去監(jiān)聽這些IO,這樣對系統(tǒng)來說肯定是并發(fā)很低的。我們?yōu)榱私鉀Q這個(gè)問題,產(chǎn)生了多路復(fù)用IO,也就是說,一次性監(jiān)聽多個(gè)fd,然后內(nèi)核發(fā)現(xiàn)fd這些fd的某些有事件觸發(fā),則返回。對于這種模型linux有3種實(shí)現(xiàn)方式

  • select
// readfds:關(guān)心讀的fd集合;writefds:關(guān)心寫的fd集合;excepttfds:異常的fd集合  
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  

傳遞3個(gè)不同事件監(jiān)聽類型fd數(shù)組下去,然后內(nèi)核對這些數(shù)據(jù)進(jìn)行for循環(huán)。如果對應(yīng)數(shù)組有對應(yīng)事件產(chǎn)生,則返回通知,如果timeout內(nèi)沒有任何事件,會(huì)告訴超時(shí)。時(shí)間復(fù)雜度比較高,是O(n)。
select基本所有平臺(tái)都支持,但是select缺點(diǎn)除了時(shí)間復(fù)雜度以外,還有只支持最大連接數(shù)1024,如果要修改這個(gè)連接數(shù),需要修改常量后重新編譯內(nèi)核。

  • poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout);  
  
struct pollfd {  
    int fd; /* file descriptor */  
    short events; /* requested events to watch */  
    short revents; /* returned events witnessed */  
};  

poll傳遞的參數(shù)是一個(gè)fd以及events在一起的結(jié)構(gòu)體數(shù)組。在內(nèi)核也是對這個(gè)數(shù)據(jù)進(jìn)行for循環(huán),遍歷查看是否有對應(yīng)事件產(chǎn)生。但是這個(gè)是沒有最大連接數(shù)的限制的。

從上面看,select 和 poll 都需要在返回后,通過遍歷文件描述符來獲取已經(jīng)就緒的 socket。事實(shí)上,同時(shí)連接的大量客戶端在一時(shí)刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會(huì)線性下降。比如我有100萬的連接數(shù),但當(dāng)前活躍的可能只有1000個(gè),但是select和poll都需要遍歷100萬次,對效率大打折扣

  • epoll
//創(chuàng)建epollFd,底層是在內(nèi)核態(tài)分配一段區(qū)域,底層數(shù)據(jù)結(jié)構(gòu)紅黑樹+雙向鏈表  
int epoll_create(int size);//創(chuàng)建一個(gè)epoll的句柄,size用來告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大  
  
//往紅黑樹中增加、刪除、更新管理的socket fd  
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
  
//這個(gè)api是用來在第一階段阻塞,等待就緒的fd。  
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);  

epoll對fd的監(jiān)聽模式有2種可以選擇,LT以及ET

  • LT 水平觸發(fā)
    無論fd是否發(fā)生變化,每次都會(huì)告知用戶態(tài)當(dāng)前fd的狀態(tài)如何
  • ET 邊緣觸發(fā)
    只有fd發(fā)生變化,才會(huì)告知用戶態(tài),告知之后,下一次不會(huì)再告訴當(dāng)前fd的狀態(tài)信息

Reactor模型介紹

Reactor模型指在使用多路復(fù)用IO時(shí),對于用戶態(tài)fd的事件管理模型

Reactor單線程模型

業(yè)務(wù)邏輯處理和fd的讀寫IO都在同一個(gè)線程實(shí)現(xiàn)

Reactor多線程模型

業(yè)務(wù)邏輯處理和fd的讀寫IO不在同一個(gè)線程實(shí)現(xiàn)

multi-reactor 多線程模型

業(yè)務(wù)處理多線程以及讀寫IO的事件分發(fā)機(jī)制也采用多線程處理

Redis的網(wǎng)絡(luò)IO模型

Redis采用單線程為何支持高并發(fā)
  • Redis使用的內(nèi)存IO,不是磁盤IO,大大降低了IO時(shí)間
  • Redis單線程,無需去考慮多線程造成的死鎖問題
  • Redis單線程,底層網(wǎng)絡(luò)IO模型使用多路復(fù)用epoll方式(如果內(nèi)核不支持epoll,可自動(dòng)切換到select或者poll,看配置信息可進(jìn)行修改)
Redis6實(shí)現(xiàn)了多線程,作用是什么

Redis6實(shí)現(xiàn)的多線程,只是對網(wǎng)絡(luò)IO讀寫處理做多線程處理,但是對命令行的操作仍然是單線程的。這樣即加快了IO處理效率,又保證了原子性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容