在linux 沒有實現(xiàn)epoll事件驅(qū)動機(jī)制之前,我們一般選擇用select或者poll等IO多路復(fù)用的方法來實現(xiàn)并發(fā)服務(wù)程序。在大數(shù)據(jù)、高并發(fā)、集群等一些名詞唱得火熱之年代,select和poll的用武之地越來越有限,風(fēng)頭已經(jīng)被epoll占盡。
select的缺點:
1. 單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,通常是1024,當(dāng)然可以更改數(shù)量,但由于select采用輪詢的方式掃描文件描述符,文件描述符數(shù)量越多,性能越差;
在linux內(nèi)核頭文件中,有這樣的定義:
#define __FD_SETSIZE 1024
2. 內(nèi)核 / 用戶空間內(nèi)存拷貝問題,select需要復(fù)制大量的句柄數(shù)據(jù)結(jié)構(gòu),產(chǎn)生巨大的開銷;
3. select返回的是含有整個句柄的數(shù)組,應(yīng)用程序需要遍歷整個數(shù)組才能發(fā)現(xiàn)哪些句柄發(fā)生了事件;
4. select的觸發(fā)方式是水平觸發(fā),應(yīng)用程序如果沒有完成對一個已經(jīng)就緒的文件描述符進(jìn)行IO操作,那么之后每次select調(diào)用還是會將這些文件描述符通知進(jìn)程。
相比select模型,poll使用鏈表保存文件描述符,因此沒有了監(jiān)視文件數(shù)量的限制,但其他三個缺點依然存在。
拿select模型為例,假設(shè)我們的服務(wù)器需要支持100萬的并發(fā)連接,則在__FD_SETSIZE 為1024的情況下,則我們至少需要開辟1k個進(jìn)程才能實現(xiàn)100萬的并發(fā)連接。
除了進(jìn)程間上下文切換的時間消耗外,從內(nèi)核/用戶空間大量的無腦內(nèi)存拷貝、數(shù)組輪詢等,是系統(tǒng)難以承受的。
因此,基于select模型的服務(wù)器程序,要達(dá)到10萬級別的并發(fā)訪問,是一個很難完成的任務(wù)。
epoll的實現(xiàn)機(jī)制與select/poll機(jī)制完全不同,上面所說的 select的缺點在epoll上不復(fù)存在。
設(shè)想一下如下場景:
有100萬個客戶端同時與一個服務(wù)器進(jìn)程保持著TCP連接。而每一時刻,通常只有幾百上千個TCP連接是活躍的(事實上大部分場景都是這種情況)。如何實現(xiàn)這樣的高并發(fā)?
在select/poll時代,服務(wù)器進(jìn)程每次都把這100萬個連接告訴操作系統(tǒng)(從用戶態(tài)復(fù)制句柄數(shù)據(jù)結(jié)構(gòu)到內(nèi)核態(tài)),讓操作系統(tǒng)內(nèi)核去查詢這些套接字上是否有事件發(fā)生,輪詢完后,再將句柄數(shù)據(jù)復(fù)制到用戶態(tài),讓服務(wù)器應(yīng)用程序輪詢處理已發(fā)生的網(wǎng)絡(luò)事件,這一過程資源消耗較大,因此,select/poll一般只能處理幾千的并發(fā)連接。
epoll的設(shè)計和實現(xiàn)與select完全不同。
epoll通過在Linux內(nèi)核中申請一個簡易的文件系統(tǒng)
(文件系統(tǒng)一般用什么數(shù)據(jù)結(jié)構(gòu)實現(xiàn)?B+樹)
把原先的select/poll調(diào)用分成了3個部分:
1)調(diào)用epoll_create()建立一個epoll對象(在epoll文件系統(tǒng)中為這個句柄對象分配資源)
2)調(diào)用epoll_ctl向epoll對象中添加這100萬個連接的套接字
3)調(diào)用epoll_wait收集發(fā)生的事件的連接
如此一來,要實現(xiàn)上面說是的場景,只需要在進(jìn)程啟動時建立一個epoll對象,然后在需要的時候向這個epoll對象中添加或者刪除連接。同時,epoll_wait的效率也非常高,因為調(diào)用epoll_wait時,并沒有一股腦的向操作系統(tǒng)復(fù)制這100萬個連接的句柄數(shù)據(jù),內(nèi)核也不需要去遍歷全部的連接。
下面來看看Linux內(nèi)核具體的epoll機(jī)制實現(xiàn)思路。
當(dāng)某一進(jìn)程調(diào)用epoll_create方法時,Linux內(nèi)核會創(chuàng)建一個eventpoll結(jié)構(gòu)體,這個結(jié)構(gòu)體中有兩個成員與epoll的使用方式密切相關(guān)。eventpoll結(jié)構(gòu)體如下所示:
struct eventpoll{
....
/*紅黑樹的根節(jié)點,這顆樹中存儲著所有添加到epoll中的需要監(jiān)控的事件*/
struct rb_root rbr;
/*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/
struct list_head rdlist;
....
};
每一個epoll對象都有一個獨立的eventpoll結(jié)構(gòu)體,用于存放通過epoll_ctl方法向epoll對象中添加進(jìn)來的事件。這些事件都會掛載在紅黑樹中,如此,重復(fù)添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n為樹的高度)。
而所有添加到epoll中的事件都會與設(shè)備(網(wǎng)卡)驅(qū)動程序建立回調(diào)關(guān)系,也就是說,當(dāng)相應(yīng)的事件發(fā)生時會調(diào)用這個回調(diào)方法。這個回調(diào)方法在內(nèi)核中叫ep_poll_callback,它會將發(fā)生的事件添加到rdlist雙鏈表中。
在epoll中,對于每一個事件,都會建立一個epitem結(jié)構(gòu)體,如下所示:
struct epitem{
struct rb_node rbn;//紅黑樹節(jié)點
struct list_head rdllink;//雙向鏈表節(jié)點
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所屬的eventpoll對象
struct epoll_event event; //期待發(fā)生的事件類型
}
當(dāng)調(diào)用epoll_wait檢查是否有事件發(fā)生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發(fā)生的事件復(fù)制到用戶態(tài),同時將事件數(shù)量返回給用戶。
通過紅黑樹和雙鏈表數(shù)據(jù)結(jié)構(gòu),并結(jié)合操作系統(tǒng)底層回調(diào)機(jī)制,造就了epoll的高效
epoll的用法
第一步:epoll_create()系統(tǒng)調(diào)用。此調(diào)用返回一個句柄,之后所有的使用都依靠這個句柄來標(biāo)識。
第二步:epoll_ctl()系統(tǒng)調(diào)用。通過此調(diào)用向epoll對象中添加、刪除、修改感興趣的事件,返回0標(biāo)識成功,返回-1表示失敗。
第三部:epoll_wait()系統(tǒng)調(diào)用。通過此調(diào)用收集收集在epoll監(jiān)控中已經(jīng)發(fā)生的事件。
本文章為面試相關(guān)技術(shù)連載內(nèi)容,大家可以持續(xù)關(guān)注小編,我將盡其所能的為大家提供技術(shù)性實踐資料、文章、視頻。
感謝大家的支持!
來自:《Java網(wǎng)絡(luò)編程面試題》
出版單位:北京尚學(xué)堂優(yōu)效學(xué)院
優(yōu)效學(xué)院由清華大學(xué)著名的IT教育領(lǐng)導(dǎo)者馬士兵老師創(chuàng)辦,是一家線上線下相互融合的互聯(lián)網(wǎng)+培訓(xùn)機(jī)構(gòu)。公司均由海外留學(xué)生和國內(nèi)行業(yè)精英人士擔(dān)任授課講師,主要成員均碩士且擁有十多年的行業(yè)經(jīng)驗。畢業(yè)學(xué)生就職于國內(nèi)BAT以及海外著名公司。優(yōu)效學(xué)院,名師執(zhí)教,高效學(xué)習(xí),成就未來。
著:張洋
優(yōu)效學(xué)院_張洋老師
11年工作經(jīng)驗 曾就職聯(lián)眾游戲(程序員)、眾信旅游(Team Leader)、精智教育(聯(lián)合創(chuàng)始人)、中國石化(大數(shù)據(jù)高級顧問) 精通javaEE體系、互聯(lián)網(wǎng)產(chǎn)品架構(gòu),熟悉Sap Bw/HANA、多個大數(shù)據(jù)項目經(jīng)驗。
20180926版