Redis中的事件處理模型
Redis並沒有使用libevent,libev,libuv等事件IO庫,而是通過ae.h、ae.c兩個文件,封裝了簡單的事件處理模型。進一步地,事件處理需要使用到係統的select、epoll等函數,在ae.c中,通過簡單的宏判斷,引入相應的實現文件,分別是ae_epoll.c、ae_evport.c、ae_kqueue.c和ae_select.c。
先來看下ae.h。首先,Redis定義了兩種事件類型,分別是aeFileEvent
和aeTimeEvent
,前者針對文件IO(包含網絡)。後者主要是一些定時或周期運行的函數,比如serverCron()
。
最主要的數據結構是struct aeEventLoop。ae.h對外提供的方法,幾乎都以此結構的實例為操作對象。
typedef struct aeEventLoop {
int maxfd; /* highest file descriptor currently registered */
int setsize; /* max number of file descriptors tracked */
long long timeEventNextId;
time_t lastTime; /* Used to detect system clock skew */
aeFileEvent *events; /* Registered events */
aeFiredEvent *fired; /* Fired events */
aeTimeEvent *timeEventHead;
int stop;
void *apidata; /* This is used for polling API specific data */
aeBeforeSleepProc *beforesleep;
aeBeforeSleepProc *aftersleep;
} aeEventLoop;
maxfd
記錄當前跟蹤的最大文件描述符,在調用某些api時便於使用,比如select()需要給一個maxfd+1的參數。
setsize
是能處理的文件描述符的最大數量。後麵可以看到這個數量關係到events
和fired
的內存分配大小。
另外一個比較重要的是apidata
,其實是指向了具體的事件處理模型相關的一個狀態數據(aeApiState
)。如果使用的是select,那麼狀態數據包含了不同的fd_set;如果使用的是epoll,狀態數據包含了epoll_create()返回的fd,還有用於接收epoll_wait()返回的存在可用事件的列表events。
aeCreateEventLoop()
便是創建aeEventLoop
的實例。在這裏可以看到,針對文件IO,events
和fired
是通過固定連續分配,按照數組的方式來使用。針對時間事件,則是timeEventHead
這樣的一個鏈表。
另外,再通過調用aeApiCreate()
來這是apidata
。按照使用的不同的係統事件處理機製,aeApiCreate()
的實現是不同的。取決於如下這段代碼:
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
#ifdef HAVE_KQUEUE
#include "ae_kqueue.c"
#else
#include "ae_select.c"
#endif
#endif
#endif
一般來說,這些HAVE_
開頭的宏,是通過configure(GNU autoconf automake係統工具生成),或者cmake判斷當前係統環境並進行定義的。但是Redis並沒有使用什麼複雜的構建工具,隻有一個Makefile,這些宏是在src/config.h中,根據操作係統或編譯器提供的宏進行判斷並進行定義。
具體調用的地方,比如src/server.c中,先進行
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
創建了aeEventLoop
。
之後便可以注冊定時任務:
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}
最後更新:2017-09-25 16:33:55