EventLoop
Overview
先給出Redis關于的定義(見下文代碼):
在ae.h中,定義了基本的數(shù)據(jù)模型和接口艘希,其實這個思想也貫穿了整個Redis的設計中硼身,Java是通過interface或者abstract class來達到抽象的方式,而C中覆享,往往通過一個頭文件佳遂,定義了基本接口,具體實現(xiàn)可以再不同文件中實現(xiàn)撒顿。
Redis對于EventLoop的處理主要集中在 ae.h/ae.c中丑罪,分為兩部分,一部分是對于文件(網(wǎng)絡)事件的處理凤壁,另一部分是對于時間事件的處理(定時任務和周期性任務)吩屹。
對于兩種不同的時間類型,Redis使用了不同的設計客扎。
文件事件主要是網(wǎng)絡socket的處理祟峦,基于不同的操作系統(tǒng)或操作系統(tǒng)版本之間,設置的網(wǎng)絡模型不同徙鱼,需要有不同的交互方式宅楞。這里,Redis的實現(xiàn)方式為抽象出與底層交互的統(tǒng)一接口袱吆,通過實現(xiàn)相應接口和加載(初始化時厌衙,會根據(jù)系統(tǒng)不同加載不同的文件,有點Java中SPI的思想)绞绒。從設計模式的角度講(以后會專門講這個婶希,筆者認為不應該糾結(jié)于設計模式本身,而是應該通過設計模式來加深對于OO的理解)蓬衡,類似于Adapter模式喻杈。
時間事件的處理分為兩類,定時任務
和周期性任務
狰晚,兩種任務的不同處理之處在于筒饰,周期性任務執(zhí)行后,會重新添加到時間任務列表中(Redis通過一個單鏈表來實現(xiàn)時間任務隊列壁晒,每次輪詢這個鏈表來執(zhí)行到期的任務瓷们,這點筆者認為使用堆
這種數(shù)據(jù)結(jié)構(gòu)來維護時間列表會不會更合理一些?)。不同于文件事件谬晕,時間事件的處理主要是通過傳入的回調(diào)函數(shù)執(zhí)行的碘裕。
aeEventLoop:
typedef struct aeEventLoop {
// 目前已注冊的最大描述符
int maxfd; /* highest file descriptor currently registered */
// 目前已追蹤的最大描述符
int setsize; /* max number of file descriptors tracked */
// 用于生成時間事件 id
long long timeEventNextId;
// 最后一次執(zhí)行時間事件的時間
time_t lastTime; /* Used to detect system clock skew */
// 已注冊的文件事件
aeFileEvent *events; /* Registered events */
// 已就緒的文件事件
aeFiredEvent *fired; /* Fired events */
// 時間事件
aeTimeEvent *timeEventHead;
// 事件處理器的開關
int stop;
// 多路復用庫的私有數(shù)據(jù)
void *apidata; /* This is used for polling API specific data */
// 在處理事件前要執(zhí)行的函數(shù)
aeBeforeSleepProc *beforesleep;
} aeEventLoop;
抽象出的接口如下:
/* Prototypes */
void aeMain(aeEventLoop *eventLoop);
int aeProcessEvents(aeEventLoop *eventLoop, int flags);
//Event Loop Related
aeEventLoop *aeCreateEventLoop(int setsize);
void aeDeleteEventLoop(aeEventLoop *eventLoop);
void aeStop(aeEventLoop *eventLoop);
//File Event Related
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData);
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
int aeGetFileEvents(aeEventLoop *eventLoop, int fd);
//Time Event Related
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc);
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id);
//Others
int aeWait(int fd, int mask, long long milliseconds);
char *aeGetApiName(void);
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
int aeGetSetSize(aeEventLoop *eventLoop);
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
文件事件
對于文件事件的處理,Redis得益于對事件的封裝上攒钳,Redis封裝的事件有以下幾種:
static int aeApiCreate(aeEventLoop *eventLoop);
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)
static int aeApiResize(aeEventLoop *eventLoop, int setsize);
static void aeApiFree(aeEventLoop *eventLoop);
static char *aeApiName(void)
對應關系為
具體怎樣運作的帮孔,放在下一節(jié)中詳細說明
時間事件
關于時間事件,Redis中主要運行的可能就是serverCron了
Interaction
本節(jié)內(nèi)容主要是以epoll為例夕玩,介紹Redis面向接口的一種設計的具體實現(xiàn)
Redis通過ae.c/ae.h中暴露出的接口和epoll(或其他socket類型的api)交互你弦,溝通的結(jié)構(gòu)為eventloop中的apidata
typedef struct aeEventLoop {
....
....
// 多路復用庫的私有數(shù)據(jù)
<font color=red><b>void *apidata; /* This is used for polling API specific data */ </b></font>
....
} aeEventLoop;
apiData 在 epoll.c 中的對應數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct aeApiState {
// epoll_event 實例描述符
int epfd;
// 事件槽
struct epoll_event *events;
} aeApiState;
初始化時,調(diào)用int aeApiCreate(aeEventLoop *eventLoop)
對eventLoop所相關的進行init操作燎孟, 如下(只記錄正常邏輯)
static int aeApiCreate(aeEventLoop *eventLoop) {
aeApiState *state = zmalloc(sizeof(aeApiState));
// 初始化事件槽空間
state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);
// 創(chuàng)建 epoll 實例, 調(diào)用epoll接口
state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
// 賦值給 eventLoop
eventLoop->apidata = state;
return 0;
}
同時禽作,函數(shù)調(diào)用方(callee)為aeEventLoop的初始化函數(shù):
aeEventLoop *aeCreateEventLoop(int setsize) {
aeEventLoop *eventLoop;
int i;
// 創(chuàng)建事件狀態(tài)結(jié)構(gòu)
if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
// 初始化文件事件結(jié)構(gòu)和已就緒文件事件結(jié)構(gòu)數(shù)組
...
// 初始化時間事件結(jié)構(gòu)
...
/** 調(diào)用API**/
if (aeApiCreate(eventLoop) == -1) goto err;
/* Events with mask == AE_NONE are not set. So let's initialize the
* vector with it. */
// 初始化監(jiān)聽事件
for (i = 0; i < setsize; i++)
eventLoop->events[i].mask = AE_NONE;
// 返回事件循環(huán)
return eventLoop;
err:
if (eventLoop) {
zfree(eventLoop->events);
zfree(eventLoop->fired);
zfree(eventLoop);
}
return NULL;
}