一文聊聊Redis中的epoll和文件事件

本篇文章給大家介紹一下redis中的文件事件,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

一文聊聊Redis中的epoll和文件事件

事件驅動

redis 服務器是事件驅動程序,分為文件事件和時間事件

  • 文件事件:socket 的可讀可寫事件
  • 定時任務

【相關推薦:Redis視頻教程

它們都被封裝到aeEventLoop結構體

typedef?struct?aeEventLoop?{ 	int?stop;?//?標識事件是否結束 	aeFileEvent?*events;?//?文件事件數組,存儲已注冊的文件事件 	aeFireEvent?*fired;?//?存儲被觸發的文件事件 	aeTimeEvent?*timteEventHead;?//?多個時間事件形成的鏈表 	void?*apidata;?//?I/O模型的封裝 	aeBeforeSleepProc?*beforesleep;?//?進程阻塞前執行 	aeBeforeSleepProc?*aftersleep;?//?進程被喚醒后執行 }?aeEventLoop;

事件驅動程序實際上也是通過while/for循環,循環等待事件的發生

while?(!?eventLoop->stop)?{ 	if?(eventLoop->beforesleep?!=?NULL) 		eventLoop->beforesleep(eventLoop) 	aeProcessEvents(eventLoop,?AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP); }

aeProcessEvents為事件處理主函數

epoll

Redis 客戶端通過 TCP socket 與服務端交互,文件事件指的就是 socket 的可讀可寫事件。一般使用非阻塞模式,相關的 I/O 多路復用有select/epoll/kqueue等,不同的操作系統不同的實現。

以epoll為例,它是 linux 內核為處理大量并發網絡連接而提出解決方案。epoll提供3個 API

  • epoll_create 創建一個 epoll 專用的文件描述符,用于后續 epoll 相關 API 調用

int?epoll_create(int?size) //?size?告知內核程序期望注冊的網絡連接數目,Linux?2.6.8后改為內核動態分配 //?返回參數是?epoll?專用的文件描述符
  • epoll_ctl 函數向 epoll 注冊、修改或刪除需要監控的事件

int?epoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event?*event) //?epfd?函數?epoll_create?返回的?epoll?文件描述符 //?op?操作類型?EPOLL_CTL_ADD:注冊事件;?EPOLL_CTL_MOD:修改網絡連接事件;?EPOLL_CTL_DEL:刪除事件 //?fd?網絡連接的?socket?文件描述符 //?event?需要監控的事件
  • epoll_wait 函數會會阻塞進程,直到監控的若干網絡連接有事件發生

int?epoll_wait(int?epfd,?struct?epoll_event?*event,?int?maxevents,?int?timeout) //?epfd?函數epoll_create返回的epoll文件描述符 //?epoll_event?作為輸出參數使用,用于回傳已觸發的事件數組 //?maxevents?每次能處理的最大事件數目 //?timeout?epoll_wait?函數阻塞超時時間,如果超過?timeout?時間還沒有事件發生,函數就不再阻塞直接返回;當?timeout?等于0是函數立即返回,timeout?等于-1時函數一直阻塞到有事件發生

文件事件

Reids 沒有直接使用 epoll 的 API,而是同時支持4種I/O多路復用模型,對這些模型的 API 進行了封裝。然后在編譯階段檢查操作系統支持的I/O多路復用模型,并按照策略來決定復用那張模型。

還是以 epoll 為例,Redis 進行了如下封裝

//?對應?epoll_create static?int?aeApiCreate(aeEventLoop?*eventLoop)  //?對應?epoll_ctl?添加事件 static?int?aeApiAddEvent(aeEventLoop?*eventLoop,?int?fd,?int?mask) //?對應?epoll_ctl?刪除事件 static?int?aeApiDelEvent(aeEventLoop?*eventLoop,?int?fd,?int?delmask)  //?對應?epoll_wait static?int?aeApiPool(aeEventLoop?*eventLoop,?struct?timeval?*tvp)

回憶一下上面提到的eventLoop結構體,其成員 apidata 指向4種I/O多路復用模型對象;events 存儲需要監控的事件數組,以 socket 文件描述符作為數組索引存取元素;fired 存儲已觸發的事件數組。

文件事件的結構體定義如下:

typedef?struct?aeFileEvent?{ 	int?mask;?//?文件事件類型?AE_READABLE?可讀事件;AE_WRITEABLE?可寫事件 	aeFileProc?*rfileProc;?//?讀事件處理函數指針 	aeFileProc?*wfileProc;?//?寫事件處理函數指針 	void?*clientData;?//?指向對應的客戶端對象 }?aeFileEvent;

看一下創建文件事件 aeCreateFileEvent 的實現

int?aeCreateFileEvent?(aeEventLoop?*eventLoop,?int?fd,?int?mask,?aeFileProc?*proc,?void?*clientData)?{ 	aeFileEvent?*fe?=?&eventLoop->evnts[fd]; 	if?(aeApiAddEvent(eventLoop,?fd,?mask)?==?-1) 		return?AE_ERR; 	fe->mask?|=?mask; 	if?(mask?&?AE_READABLE)?fe->rfileProc?=?proc; 	if?(mask?&?AE_WRITABLE)?fe->wfileProc?=?proc; 	fe->clientData?=?clientData; 	return?AE_OK; }

Redis 服務器會通過創建各類文件事件來處理事務,比如:

  • 啟動時創建 socket 并監聽,等待客戶端連接
aeCreateFileEvent(server.el,?server.ipfd[j],?AE_READABLE,?acceptTcpHandler,?NULL);
  • 客戶端與服務器建立 socket 連接之后,服務器會等待客戶端的命令請求
aeCreateFileEvent(server.el,?fd,?AE_READABLLE,?readQueryFromClient,?c);
  • 服務器處理完客戶端的命令請求之后,命令回復會暫時緩存在client結構體的buf緩沖區,待客戶端文件描述符的可寫事件發生時,才會真正往客戶端發送命令回復
aeCreateFileEvent(server.el,?c->fd,?AE_READABLLE,?sendReplyToClient,?c);

Redis 所有事件的執行都是通過aeProcessEvents函數來控制。在其中,執行文件事件會出現阻塞情況(epoll_wait),如果阻塞事件太長了,會妨礙到時間事件(定時)的執行,為避免出現這種情況,在實現文件事件時傳入的等待時間,是計算最早發生的時間事件得到的

int?aeProcessEvents(aeEventLoop?*eventLoop,?int?flags)?{ 	shortest?=?aeSearchNearestTimer(eventLoop); 	long?long?ms?=?(shortest->when_sec?-?now_sec)?*?1000?+? 		shortest->when_ms?-?now_ms;  	//?阻塞事件發生 	numevents?=?aeApiPoll(eventLoop,?ms);  	for?(j=0;?j?events[eventLoop->fired[j]].fd]; 		//?處理文件事件,即根據類型執行rfileProc或wfileProc 	}  	//?處理時間事件 	processed?+=?processTimeEvents(eventLoop); }

總結

現在我們來整體看一下 Redis 服務器相應命令的流程

一文聊聊Redis中的epoll和文件事件

aeMain 函數通過調用 aeProcessEvents 函數來進行文件事件和時間事件的調度和執行。aeEventLoop 中記錄了事件相關的信息。首先通過 aeSearchNearestTimer 函數獲取最短的時間事件的執行時間間隔n,然后調用 aeApiPoll 函數獲取監聽到的套接字,最后執行與套接字向對應的事件處理函數 rfileProc 和 wfileProc,最后再執行時間事件函數 processTimeEvents。

一次完整的客戶端與服務端連接事件:

  • 器監聽套件字的 AE_READABLE 事件,當客戶端發送連接請求產生 AE_READABLE ?事件,服務端會對客戶端的連接請求進行應答,將客戶端套接字的 AE_READABLE 事件與命令請求處理函數(aeFileProc),客戶端可以向服務端發送命令請求了

  • 端向服務端發送一個命令請求,客戶端套接字將產生 AE_READABLE 事件,引發命令處理器去執行,執行命令將產生相應的命令回復,服務端將客戶端套接字的 AE_WRITABLE 事件與命令回復處理函數(aeFileProc)關聯

  • 端嘗試讀取命令回復時,客戶端套接字將產生 AE_WRITABLE 事件,觸發命令回復處理器執行,當命令回復處理器將命令回復全部寫入套接字之后,服務器就會接觸客戶端套接字的 AE_WRITABLE 事件與命令回復處理函數(aeFileProc)之間的關聯

更多編程相關知識,請訪問:Redis視頻教程!!

? 版權聲明
THE END
喜歡就支持一下吧
點贊5 分享