一拭嫁、簡(jiǎn)述
IO 多路復(fù)用是一種同步 IO 模型葱椭,實(shí)現(xiàn)一個(gè)線程可以監(jiān)視多個(gè)文件句柄鸿秆;一旦某個(gè)文件句柄就緒,就能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫操作帚稠;沒有文件句柄就緒時(shí)會(huì)阻塞應(yīng)用程序谣旁,交出 cpu。IO 是指網(wǎng)絡(luò) IO滋早,多路指多個(gè) TCP 連接(即 socket 或者 channel)榄审,復(fù)用指復(fù)用一個(gè)或幾個(gè)線程。意思說一個(gè)或一組線程處理多個(gè) TCP 連接杆麸。最大優(yōu)勢(shì)是減少系統(tǒng)開銷小搁进,不必創(chuàng)建過多的進(jìn)程/線程,也不必維護(hù)這些進(jìn)程/線程昔头。IO 多路復(fù)用的三種實(shí)現(xiàn)方式:select饼问、poll、epoll揭斧。
二莱革、select 機(jī)制
1??基本原理:
客戶端操作服務(wù)器時(shí)就會(huì)產(chǎn)生這三種文件描述符(簡(jiǎn)稱fd):writefds(寫)、readfds(讀)讹开、和 exceptfds(異常)盅视。select 會(huì)阻塞住監(jiān)視 3 類文件描述符,等有數(shù)據(jù)萧吠、可讀左冬、可寫、出異持叫停或超時(shí)就會(huì)返回;返回后通過遍歷 fdset 整個(gè)數(shù)組來找到就緒的描述符 fd,然后進(jìn)行對(duì)應(yīng)的 IO 操作狰腌。
2??優(yōu)點(diǎn):
幾乎在所有的平臺(tái)上支持除破,跨平臺(tái)支持性好
3??缺點(diǎn):
- 由于是采用輪詢方式全盤掃描,會(huì)隨著文件描述符 FD 數(shù)量增多而性能下降琼腔。
- 每次調(diào)用 select()瑰枫,都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài),并進(jìn)行遍歷(消息傳遞都是從內(nèi)核到用戶空間)丹莲。
- 單個(gè)進(jìn)程打開的 FD 是有限制(通過
FD_SETSIZE
設(shè)置)的光坝,默認(rèn)是 1024 個(gè),可修改宏定義甥材,但是效率仍然慢盯另。
三、poll 機(jī)制
1??基本原理與 select 一致洲赵,也是輪詢+遍歷鸳惯。唯一的區(qū)別就是 poll 沒有最大文件描述符限制(使用鏈表的方式存儲(chǔ) fd)。
2??poll 缺點(diǎn)
- 由于是采用輪詢方式全盤掃描叠萍,會(huì)隨著文件描述符 FD 數(shù)量增多而性能下降芝发。
- 每次調(diào)用 select(),都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài)苛谷,并進(jìn)行遍歷(消息傳遞都是從內(nèi)核到用戶空間)辅鲸。
四、epoll機(jī)制
1??基本原理:
沒有 fd 個(gè)數(shù)限制腹殿,用戶態(tài)拷貝到內(nèi)核態(tài)只需要一次独悴,使用時(shí)間通知機(jī)制來觸發(fā)。通過 epoll_ctl 注冊(cè) fd赫蛇,一旦 fd 就緒就會(huì)通過 callback 回調(diào)機(jī)制來激活對(duì)應(yīng) fd绵患,進(jìn)行相關(guān)的 io 操作。
epoll 之所以高性能是得益于它的三個(gè)函數(shù):
- epoll_create() 系統(tǒng)啟動(dòng)時(shí)悟耘,在 Linux 內(nèi)核里面申請(qǐng)一個(gè) B+樹結(jié)構(gòu)文件系統(tǒng)落蝙,返回 epoll 對(duì)象,也是一個(gè) fd暂幼。
- epoll_ctl() 每新建一個(gè)連接筏勒,都通過該函數(shù)操作 epoll 對(duì)象,在這個(gè)對(duì)象里面修改添加刪除對(duì)應(yīng)的鏈接 fd旺嬉,綁定一個(gè) callback 函數(shù)
- epoll_wait() 輪訓(xùn)所有的callback集合管行,并完成對(duì)應(yīng)的 IO 操作
2??優(yōu)點(diǎn):
沒 fd 這個(gè)限制,所支持的 FD 上限是操作系統(tǒng)的最大文件句柄數(shù)邪媳,1G 內(nèi)存大概支持 10 萬(wàn)個(gè)句柄捐顷。效率提高荡陷,使用回調(diào)通知而不是輪詢的方式,不會(huì)隨著 FD 數(shù)目的增加效率下降迅涮。內(nèi)核和用戶空間 mmap 同一塊內(nèi)存實(shí)現(xiàn)(mmap 是一種內(nèi)存映射文件的方法废赞,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間)
3??epoll缺點(diǎn):
epoll 只能工作在 linux 下。
4??epoll 應(yīng)用:redis叮姑、nginx
五唉地、epoll 水平觸發(fā)(LT)與邊緣觸發(fā)(ET)的區(qū)別
epoll 有 epoll LT 和 epoll ET 兩種觸發(fā)模式,LT 是默認(rèn)的模式传透,ET 是“高速”模式耘沼。
1??LT 模式下,只要這個(gè) fd 還有數(shù)據(jù)可讀朱盐,每次 epoll_wait 都會(huì)返回它的事件群嗤,提醒用戶程序去操作
2??ET 模式下,它只會(huì)提示一次托享,直到下次再有數(shù)據(jù)流入之前都不會(huì)再提示了骚烧,無論 fd 中是否還有數(shù)據(jù)可讀。所以在 ET 模式下闰围,read 一個(gè) fd 的時(shí)候一定要把它的 buffer 讀完赃绊,或者遇到 EAGAIN 錯(cuò)誤。
六羡榴、select/poll/epoll之間的區(qū)別
七碧查、為什么有IO多路復(fù)用機(jī)制
沒有 IO 多路復(fù)用機(jī)制時(shí),有 BIO校仑、NIO 兩種實(shí)現(xiàn)方式忠售,但有一些問題。
1??同步阻塞(BIO)
服務(wù)端采用單線程迄沫,當(dāng) accept 一個(gè)請(qǐng)求后稻扬,在 recv 或 send 調(diào)用阻塞時(shí),將無法 accept 其他請(qǐng)求(必須等上一個(gè)請(qǐng)求 recv 或 send 完)羊瘩,無法處理并發(fā)泰佳。
服務(wù)器端采用多線程,當(dāng) accept 一個(gè)請(qǐng)求后尘吗,開啟線程進(jìn)行 recv逝她,可以完成并發(fā)處理,但隨著請(qǐng)求數(shù)增加需要增加系統(tǒng)線程睬捶,大量的線程占用很大的內(nèi)存空間黔宛,并且線程切換會(huì)帶來很大的開銷,10000 個(gè)線程真正發(fā)生讀寫事件的線程數(shù)不會(huì)超過 20%擒贸,每次 accept 都開一個(gè)線程也是一種資源浪費(fèi)臀晃。
2??同步非阻塞(NIO)
服務(wù)器端當(dāng) accept 一個(gè)請(qǐng)求后觉渴,加入 fds 集合,每次輪詢一遍 fds 集合 recv(非阻塞)數(shù)據(jù)积仗,沒有數(shù)據(jù)則立即返回錯(cuò)誤疆拘,每次輪詢所有 fd(包括沒有發(fā)生讀寫事件的fd)會(huì)很浪費(fèi) cpu蜕猫。
3??IO 多路復(fù)用
服務(wù)器端采用單線程通過 select/epoll 等系統(tǒng)調(diào)用獲取 fd 列表寂曹,遍歷有事件的 fd 進(jìn)行 accept/recv/send,使其能支持更多的并發(fā)連接請(qǐng)求回右。
八隆圆、理解 IO 多路復(fù)用機(jī)制
小王在 S 城開了一家快遞店,負(fù)責(zé)同城快送服務(wù)翔烁。小王因?yàn)橘Y金限制渺氧,雇傭了一批快遞員,然后小王發(fā)現(xiàn)資金不夠了蹬屹,只夠買一輛車送快遞侣背。
1??【經(jīng)營(yíng)方式一】
客戶每送來一份快遞,小王就讓一個(gè)快遞員盯著慨默,然后快遞員開車去送快遞贩耐。慢慢的小王就發(fā)現(xiàn)了這種經(jīng)營(yíng)方式存在下述問題:
- 幾十個(gè)快遞員基本上時(shí)間都花在了搶車上了,大部分快遞員都處在閑置狀態(tài)厦取,誰(shuí)搶到了車潮太,誰(shuí)就能去送快遞。
- 隨著快遞的增多虾攻,快遞員也越來越多铡买,小王發(fā)現(xiàn)快遞店里越來越擠,沒辦法雇傭新的快遞員了霎箍。
- 快遞員之間的協(xié)調(diào)很費(fèi)時(shí)間奇钞。
2??【經(jīng)營(yíng)方式二】
小王只雇傭一個(gè)快遞員。然后呢漂坏,客戶送來的快遞景埃,小王按送達(dá)地點(diǎn)標(biāo)注好,然后依次放在一個(gè)地方樊拓。最后纠亚,那個(gè)快遞員依次去取快遞,一次拿一個(gè)筋夏,然后開著車去送快遞蒂胞,送好了就回來拿下一個(gè)快遞。
3??【對(duì)比】
兩種經(jīng)營(yíng)方式對(duì)比条篷,第二種明顯效率更高骗随,更好蛤织。在上述比喻中:
- 每個(gè)快遞員------------------>每個(gè)線程
- 每個(gè)快遞-------------------->每個(gè)socket(IO流)
- 快遞的送達(dá)地點(diǎn)-------------->socket的不同狀態(tài)
- 客戶送快遞請(qǐng)求-------------->來自客戶端的請(qǐng)求
- 小王的經(jīng)營(yíng)方式-------------->服務(wù)端運(yùn)行的代碼
- 一輛車---------------------->CPU的核數(shù)
4?? 于是有如下結(jié)論:
- 【經(jīng)營(yíng)方式一】就是傳統(tǒng)的并發(fā)模型,每個(gè) IO 流(快遞)都有一個(gè)新的線程(快遞員)管理鸿染。
- 【經(jīng)營(yíng)方式二】就是 IO 多路復(fù)用指蚜。只有單個(gè)線程(一個(gè)快遞員),通過跟蹤每個(gè) IO 流的狀態(tài)(每個(gè)快遞的送達(dá)地點(diǎn))涨椒,來管理多個(gè) IO 流摊鸡。
類比到真實(shí)的redis線程模型,如圖:
如圖蚕冬,簡(jiǎn)單來說免猾,就是 redis-client 在操作的時(shí)候,會(huì)產(chǎn)生具有不同事件類型的 socket囤热。在服務(wù)端猎提,有一段 IO 多路復(fù)用程序,將其置入隊(duì)列之中旁蔼。然后锨苏,文件事件分派器,依次去隊(duì)列中取棺聊,轉(zhuǎn)發(fā)到不同的事件處理器中伞租。需要說明的是,這個(gè) IO 多路復(fù)用機(jī)制躺屁,redis 還提供了 select肯夏、epoll、evport犀暑、kqueue 等多路復(fù)用函數(shù)庫(kù)驯击。
九、示例
100 萬(wàn)個(gè)連接耐亏,里面有 1 萬(wàn)個(gè)連接是活躍徊都,可以對(duì)比 select、poll广辰、epoll 的性能表現(xiàn):
1??select:不修改宏定義默認(rèn)是 1024暇矫,則需要100w/1024=977
個(gè)進(jìn)程才可以支持 100 萬(wàn)連接,會(huì)使得 CPU 性能特別的差择吊。
2??poll:沒有最大文件描述符限制李根,100 萬(wàn)個(gè)鏈接則需要 100 萬(wàn)個(gè) fd,遍歷都響應(yīng)不過來了几睛,還有空間的拷貝消耗大量的資源房轿。
3??epoll:請(qǐng)求進(jìn)來時(shí)就創(chuàng)建 fd 并綁定一個(gè) callback,主需要遍歷 1 萬(wàn)個(gè)活躍連接的 callback 即可,即高效又不用內(nèi)存拷貝囱持。