PHP 高性能背后事件驅(qū)動 Epoll
概述
使用過開源高性能的 PHP 應(yīng)用容器 Workerman同學(xué)都知道,為了能支持更大的并發(fā)連接數(shù),必須安裝event擴(kuò)展。這里說的event擴(kuò)展就是libevent,它是一個輕量級的基于事件驅(qū)動的高性能的開源網(wǎng)絡(luò)庫。
libevent 是一款跨平臺、高性能的 I/O 事件通知庫,而 epoll 是 Linux 內(nèi)核提供的 原生 I/O 多路復(fù)用機(jī)制—— 二者是「上層封裝庫」與「底層實現(xiàn)」的關(guān)系:libevent 以 epoll 為 Linux 平臺的核心驅(qū)動,同時兼容其他操作系統(tǒng)的 I/O 多路復(fù)用機(jī)制(如 Windows 的 IOCP、BSD 的 kqueue),為開發(fā)者提供統(tǒng)一的事件驅(qū)動編程接口。
epoll 介紹
epoll 是Linux[1]內(nèi)核的可擴(kuò)展I/O事件通知機(jī)制[1]。于Linux 2.5.44首度登場,它設(shè)計目的旨在取代既有POSIX[2]`select(2)`[3]與poll(2)系統(tǒng)函數(shù)[4],讓需要大量操作文件描述符[5]的程序得以發(fā)揮更優(yōu)異的性能(舉例來說:舊有的系統(tǒng)函數(shù)所花費(fèi)的時間復(fù)雜度為O(n),epoll的時間復(fù)雜度O(log n))。epoll 實現(xiàn)的功能與 poll 類似,都是監(jiān)聽多個文件描述符上的事件。
epoll與FreeBSD[6]的[kqueue](https://zh.wikipedia.org/wiki/Kqueue "Kqueue")類似,底層都是由可配置的操作系統(tǒng)內(nèi)核對象建構(gòu)而成,并以文件描述符(file descriptor)的形式呈現(xiàn)于用戶空間[7]。epoll通過使用紅黑樹[8](RB-tree)搜索被監(jiān)控的文件描述符(file descriptor)。
在 epoll 實例上注冊事件時,epoll 會將該事件添加到 epoll 實例的紅黑樹上并注冊一個回調(diào)函數(shù),當(dāng)事件發(fā)生時會將事件添加到就緒鏈表中。https://zh.wikipedia.org/wiki/Epoll
epoll 高效最關(guān)鍵的兩點(diǎn)
1、內(nèi)部管理 fd 使用了高效的紅黑樹結(jié)構(gòu)管理,做到了增刪改之后性能的優(yōu)化和平衡;
2、epoll 池添加 fd 的時候,調(diào)用 file_operations->poll ,把這個 fd 就緒之后的回調(diào)路徑安排好。通過事件通知的形式,做到最高效的運(yùn)行
3、epoll 池核心的兩個數(shù)據(jù)結(jié)構(gòu):紅黑樹和就緒列表
紅黑樹是為了應(yīng)對用戶的增刪改需求,就緒列表是 fd 事件就緒之后放置的特殊地點(diǎn),epoll 池只需要遍歷這個就緒鏈表,就能給用戶返回所有已經(jīng)就緒的 fd 數(shù)組
哪些 fd 可以用 epoll 來管理?
再來思考另外一個問題:由于并不是所有的 fd 對應(yīng)的文件系統(tǒng)都實現(xiàn)了 poll 接口,所以自然并不是所有的 fd 都可以放進(jìn) epoll 池,那么有哪些文件系統(tǒng)的 file_operations 實現(xiàn)了 poll 接口?
首先說,類似 ext2,ext4,xfs 這種常規(guī)的文件系統(tǒng)是沒有實現(xiàn)的,換句話說,這些你常見的、真的是文件的文件系統(tǒng)反倒是用不了 epoll 機(jī)制**的。
那誰支持呢?
最常見的就是網(wǎng)絡(luò)套接字:socket 。網(wǎng)絡(luò)也是 epoll 池最常見的應(yīng)用地點(diǎn)。Linux 下萬物皆文件,socket 實現(xiàn)了一套socket_file_operations的邏輯( net/socket.c ):
static const struct file_operations socket_file_ops = {
.read_iter = sock_read_iter,
.write_iter = sock_write_iter,
.poll = sock_poll,
// ...
};我們看到 socket 實現(xiàn)了 poll 調(diào)用,所以 socket fd 是天然可以放到 epoll 池管理的。
還有支持的嗎?
有的,很多。其實 Linux 下還有兩個很典型的 fd ,常常也會放到 epoll 池里。
- eventfd:eventfd 實現(xiàn)非常簡單,故名思義就是專門用來做事件通知用的。使用系統(tǒng)調(diào)用
eventfd創(chuàng)建,這種文件 fd 無法傳輸數(shù)據(jù),只用來傳輸事件,常常用于生產(chǎn)消費(fèi)者模式的事件實現(xiàn); - timerfd:這是一種定時器 fd,使用
timerfd_create創(chuàng)建,到時間點(diǎn)觸發(fā)可讀事件;
小結(jié)一下:
- ext2,ext4,xfs 等這種真正的文件系統(tǒng)的 fd ,無法使用 epoll 管理;
- socket fd,eventfd,timerfd 這些實現(xiàn)了 poll 調(diào)用的可以放到 epoll 池進(jìn)行管理;
其實,在 Linux 的模塊劃分中,eventfd,timerfd,epoll 池都是文件系統(tǒng)的一種模塊實現(xiàn)。
總結(jié)
- IO 多路復(fù)用的原始實現(xiàn)很簡單,就是一個 1 對多的服務(wù)模式,一個
loop對應(yīng)處理多個fd; - IO 多路復(fù)用想要做到真正的高效,必須要內(nèi)核機(jī)制提供。因為 IO 的處理和完成是在內(nèi)核,如果內(nèi)核不幫忙,用戶態(tài)的程序根本無法精確的抓到處理時機(jī);
- fd 記得要設(shè)置成非阻塞的哦,切記;
- epoll 池通過高效的內(nèi)部管理結(jié)構(gòu),并且結(jié)合操作系統(tǒng)提供的 poll 事件注冊機(jī)制,實現(xiàn)了高效的
fd事件管理,為高并發(fā)的IO處理提供了前提條件; epoll全名eventpoll,在 Linux 內(nèi)核下以一個文件系統(tǒng)模塊的形式實現(xiàn),所以有人常說 epoll 其實本身就是文件系統(tǒng)也是對的;socketfd,eventfd,timerfd這三種”文件“fd 實現(xiàn)了 poll 接口,所以網(wǎng)絡(luò) fd,事件fd,定時器fd 都可以使用 epoll_ctl 注冊到池子里。我們最常見的就是網(wǎng)絡(luò)fd的多路復(fù)用;- ext2,ext4,xfs 這種真正意義的文件系統(tǒng)反倒沒有提供 poll 接口實現(xiàn),所以不能用 epoll 池來管理其句柄。那文件就無法使用 epoll 機(jī)制了嗎?不是的,有一個庫叫做 libaio ,通過這個庫我們可以間接的讓文件使用 epoll 通知事件;
參考資料
[1] Linux: https://zh.wikipedia.org/wiki/Linux
[2] POSIX: https://zh.wikipedia.org/wiki/POSIX
[3] Select (Unix): https://zh.wikipedia.org/wiki/Select_(Unix)
[4] 系統(tǒng)調(diào)用: https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8
[5] 文件描述符: https://zh.wikipedia.org/wiki/%E6%AA%94%E6%A1%88%E6%8F%8F%E8%BF%B0%E5%AD%90
[6] FreeBSD: https://zh.wikipedia.org/wiki/FreeBSD
[7] 用戶空間: https://zh.wikipedia.org/wiki/%E4%BD%BF%E7%94%A8%E8%80%85%E7%A9%BA%E9%96%93
[8] 紅黑樹: https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91



























