Redis源碼設(shè)計(jì)剖析之事件處理示例詳解
1. Redis事件介紹
Redis服務(wù)器是一個(gè)事件驅(qū)動(dòng)程序,所謂事件驅(qū)動(dòng)就是輸入一條命令并且按下回車,然后消息被組裝成Redis協(xié)議的格式發(fā)送給Redis服務(wù)器,這個(gè)時(shí)候就會(huì)產(chǎn)生一個(gè)事件,Redis服務(wù)器會(huì)接收改命令,處理該命令和發(fā)送回復(fù),而當(dāng)我們沒(méi)有與服務(wù)器進(jìn)行交互時(shí),服務(wù)器就會(huì)處于阻塞等待狀態(tài),它會(huì)讓出CPU然后進(jìn)入睡眠狀態(tài),當(dāng)事件觸發(fā)時(shí),就會(huì)被操作系統(tǒng)喚醒.
而Redis服務(wù)器需要處理以下兩類事件:
文件事件:Redis 服務(wù)器通過(guò)套接字與客戶端(或者其他Redis服務(wù)器)進(jìn)行連接,而文件事件就是服務(wù)器對(duì)套接字操作的抽象. 服務(wù)器與客戶端(或者其他服務(wù)器)的通信會(huì)產(chǎn)生相應(yīng)的文件事件,而服務(wù)器則通過(guò)監(jiān)聽(tīng)并處理這些事件來(lái)完成一系列網(wǎng)絡(luò)通信操作.
時(shí)間事件:Redis 服務(wù)器中的一些操作(比如serverCron函數(shù))需要在給定的時(shí)間點(diǎn)執(zhí)行,而時(shí)間事件就是服務(wù)器對(duì)這類定時(shí)操作的抽象.
2. 事件的抽象
Redis把文件事件和時(shí)間事件分別抽象成一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)管理.
2.1 文件事件結(jié)構(gòu)
typedef struct aeFileEvent {
// 文件時(shí)間類型:AE_NONE,AE_READABLE,AE_WRITABLE
int mask;
// 可讀處理函數(shù)
aeFileProc *rfileProc;
// 可寫處理函數(shù)
aeFileProc *wfileProc;
// 客戶端傳入的數(shù)據(jù)
void *clientData;
} aeFileEvent; //文件事件
其中rfileProc和wfileProc成員分別為兩個(gè)函數(shù)指針,他們的原型為:
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
該函數(shù)是回調(diào)函數(shù),如果當(dāng)前文件事件所指定的事件類型發(fā)生時(shí),則會(huì)調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)來(lái)處理該事件.
當(dāng)事件就緒的時(shí)候,我們需要知道文件事件的文件描述符還有事件類型才能對(duì)于鎖定該事件,因此定義了aeFiredEvent結(jié)構(gòu)統(tǒng)一管理:
typedef struct aeFiredEvent {
// 就緒事件的文件描述符
int fd;
// 就緒事件類型:AE_NONE,AE_READABLE,AE_WRITABLE
int mask;
} aeFiredEvent; //就緒事件
文件事件的類型:
#define AE_NONE 0 //未設(shè)置 #define AE_READABLE 1 //事件可讀 #define AE_WRITABLE 2 //事件可寫
2.2 時(shí)間事件結(jié)構(gòu)
typedef struct aeTimeEvent {
// 時(shí)間事件的id
long long id;
// 時(shí)間事件到達(dá)的時(shí)間的秒數(shù)
long when_sec; /* seconds */
// 時(shí)間事件到達(dá)的時(shí)間的毫秒數(shù)
long when_ms; /* milliseconds */
// 時(shí)間事件處理函數(shù)
aeTimeProc *timeProc;
// 時(shí)間事件終結(jié)函數(shù)
aeEventFinalizerProc *finalizerProc;
// 客戶端傳入的數(shù)據(jù)
void *clientData;
// 指向下一個(gè)時(shí)間事件
struct aeTimeEvent *next;
} aeTimeEvent; //時(shí)間事件
可以看出,時(shí)間事件的結(jié)構(gòu)就是一個(gè)鏈表的節(jié)點(diǎn),因?yàn)?code>struct aeTimeEvent *next是指向下一個(gè)時(shí)間事件的指針.
和文件事件一樣,當(dāng)時(shí)間事件所指定的事件發(fā)生時(shí),也會(huì)調(diào)用對(duì)應(yīng)的回調(diào)函數(shù),結(jié)構(gòu)成員timeProc和finalizerProc都是回調(diào)函數(shù),函數(shù)原型如下:
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
雖然對(duì)文件事件和時(shí)間事件都做了抽象,Redis仍然需要對(duì)事件做一個(gè)整體的抽象,用來(lái)描述一個(gè)事件的狀態(tài). 也就是下面要介紹的事件狀態(tài)結(jié)構(gòu):aeEventLoop.
2.3 事件狀態(tài)結(jié)構(gòu)
typedef struct aeEventLoop {
// 當(dāng)前已注冊(cè)的最大的文件描述符
int maxfd; /* highest file descriptor currently registered */
// 文件描述符監(jiān)聽(tīng)集合的大小
int setsize; /* max number of file descriptors tracked */
// 下一個(gè)時(shí)間事件的ID
long long timeEventNextId;
// 最后一次執(zhí)行事件的時(shí)間
time_t lastTime; /* Used to detect system clock skew */
// 注冊(cè)的文件事件表
aeFileEvent *events; /* Registered events */
// 已就緒的文件事件表
aeFiredEvent *fired; /* Fired events */
// 時(shí)間事件的頭節(jié)點(diǎn)指針
aeTimeEvent *timeEventHead;
// 事件處理開關(guān)
int stop;
// 多路復(fù)用庫(kù)的事件狀態(tài)數(shù)據(jù)
void *apidata; /* This is used for polling API specific data */
// 執(zhí)行處理事件之前的函數(shù)
aeBeforeSleepProc *beforesleep;
} aeEventLoop; //事件輪詢的狀態(tài)結(jié)構(gòu)
aeEventLoop結(jié)構(gòu)保存了一個(gè)void *類型的萬(wàn)能指針apidata,用來(lái)保存輪詢事件的狀態(tài),也就是保存底層調(diào)用的多路復(fù)用庫(kù)的事件狀態(tài).
Redis的 I/O多路復(fù)用程序的所有功能都是通過(guò)包裝常見(jiàn)的select、epoll、evport和kqueue這些I/O多路復(fù)用函數(shù)庫(kù)來(lái)實(shí)現(xiàn)的,每個(gè)I/O多路復(fù)用函數(shù)庫(kù)在Redis源碼中都對(duì)應(yīng)著一個(gè)單獨(dú)的文件,比如ae_select.c、ae_epoll.c等等.
他們?cè)诰幾g階段,會(huì)根據(jù)不同的系統(tǒng)選擇性能最高的一個(gè)多路復(fù)用庫(kù)作為Redis的多路復(fù)用程序?qū)崿F(xiàn),而且所有庫(kù)的API都是相同的,這就可以讓Redis多路復(fù)用程序的底層可以互換.
下面是具體選擇庫(kù)的源碼:
// IO復(fù)用的選擇,性能依次下降,Linux支持 "ae_epoll.c" 和 "ae_select.c"
#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
也可以通過(guò)命令INFO server來(lái)查看當(dāng)前使用的是哪個(gè)多路復(fù)用庫(kù):

可以看到Linux下默認(rèn)使用的是epoll多路復(fù)用庫(kù),那么apidata保存的就是epoll模型的事件狀態(tài)結(jié)構(gòu),它在ae_epoll.c源文件中:
typedef struct aeApiState {
// epoll事件的文件描述符
int epfd;
// 事件表
struct epoll_event *events;
} aeApiState; // 事件的狀態(tài)
epoll模型的struct epoll_event結(jié)構(gòu)中定義著epoll事件的類型,比如EPOLLIN、EPOLLOUT等等,但是Redis的文件結(jié)構(gòu)aeFileEvent中也在mask中定義了自己的事件類型,例如:AE_READABLE、AE_WRITABLE等等,于是就需要實(shí)現(xiàn)一個(gè)中間層將兩者的事件類型相聯(lián)系起來(lái),這就是之前提到的ae_epoll.c文件中實(shí)現(xiàn)的相同的API:
// 創(chuàng)建一個(gè)epoll實(shí)例,保存到eventLoop中 static int aeApiCreate(aeEventLoop *eventLoop) // 調(diào)整事件表的大小 static int aeApiResize(aeEventLoop *eventLoop, int setsize) // 釋放epoll實(shí)例和事件表空間 static void aeApiFree(aeEventLoop *eventLoop) // 在epfd標(biāo)識(shí)的事件表上注冊(cè)fd的事件 static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) // 在epfd標(biāo)識(shí)的事件表上注刪除fd的事件 static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) // 等待所監(jiān)聽(tīng)文件描述符上有事件發(fā)生 static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) // 返回正在使用的IO多路復(fù)用庫(kù)的名字 static char *aeApiName(void)
這些API會(huì)講epoll的底層函數(shù)封裝起來(lái),Redis實(shí)現(xiàn)事件時(shí),只需要調(diào)用這些接口即可.
我們以下面兩個(gè)API的源碼舉例:
aeApiAddEvent
該函數(shù)會(huì)向Redis事件狀態(tài)結(jié)構(gòu)aeEventLoop的事件表event注冊(cè)一個(gè)事件,對(duì)應(yīng)的是epoll_ctl函數(shù).
// 在epfd標(biāo)識(shí)的事件表上注冊(cè)fd的事件
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
aeApiState *state = eventLoop->apidata;
struct epoll_event ee = {0};
// EPOLL_CTL_ADD,向epfd注冊(cè)fd的上的event
// EPOLL_CTL_MOD,修改fd已注冊(cè)的event
// #define AE_NONE 0 //未設(shè)置
// #define AE_READABLE 1 //事件可讀
// #define AE_WRITABLE 2 //事件可寫
// 判斷fd事件的操作,如果沒(méi)有設(shè)置事件,則進(jìn)行關(guān)聯(lián)mask類型事件,否則進(jìn)行修改
int op = eventLoop->events[fd].mask == AE_NONE ?
EPOLL_CTL_ADD : EPOLL_CTL_MOD;
// struct epoll_event {
// uint32_t events; /* Epoll events */
// epoll_data_t data; /* User data variable */
// };
ee.events = 0;
// 如果是修改事件,合并之前的事件類型
mask |= eventLoop->events[fd].mask; /* Merge old events */
// 根據(jù)mask映射epoll的事件類型
if (mask & AE_READABLE) ee.events |= EPOLLIN; //讀事件
if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; //寫事件
ee.data.fd = fd; //設(shè)置事件所從屬的目標(biāo)文件描述符
// 將ee事件注冊(cè)到epoll中
if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
return 0;
}
aeApiPoll
等待所監(jiān)聽(tīng)文件描述符上有事件發(fā)生,對(duì)應(yīng)著底層的epoll_wait函數(shù).
// 等待所監(jiān)聽(tīng)文件描述符上有事件發(fā)生
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata;
int retval, numevents = 0;
// 監(jiān)聽(tīng)事件表上是否有事件發(fā)生
retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
// 至少有一個(gè)就緒的事件
if (retval > 0) {
int j;
numevents = retval;
// 遍歷就緒的事件表,將其加入到eventLoop的就緒事件表中
for (j = 0; j < numevents; j++) {
int mask = 0;
struct epoll_event *e = state->events+j;
// 根據(jù)就緒的事件類型,設(shè)置mask
if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
if (e->events & EPOLLERR) mask |= AE_WRITABLE;
if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
// 添加到就緒事件表中
eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask;
}
}
// 返回就緒的事件個(gè)數(shù)
return numevents;
}
3. 事件的實(shí)現(xiàn)
事件的所有源碼都定義在ae.c源文件中,先從aeMain函數(shù)說(shuō)起.
// 事件輪詢的主函數(shù)
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
// 一直處理事件
while (!eventLoop->stop) {
// 執(zhí)行處理事件之前的函數(shù)
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
//處理到時(shí)的時(shí)間事件和就緒的文件事件
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
可以看到,如果服務(wù)器一直處理事件,那么就是一個(gè)死循環(huán),而一個(gè)最典型的事件驅(qū)動(dòng),就是一個(gè)死循環(huán). 在循環(huán)中,程序會(huì)調(diào)用處理事件的函數(shù)aeProcessEvents(),它的參數(shù)是一個(gè)事件狀態(tài)結(jié)構(gòu)aeEventLoop和AE_ALL_EVENTS.
事件類型的宏定義,在ae.h頭文件中:
#define AE_FILE_EVENTS 1 //文件事件 #define AE_TIME_EVENTS 2 //時(shí)間事件 #define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS) //文件和時(shí)間事件 #define AE_DONT_WAIT 4
// 處理到時(shí)的時(shí)間事件和就緒的文件事件
// 如果flags = 0,函數(shù)什么都不做,直接返回
// 如果flags設(shè)置了 AE_ALL_EVENTS ,則執(zhí)行所有類型的事件
// 如果flags設(shè)置了 AE_FILE_EVENTS ,則執(zhí)行文件事件
// 如果flags設(shè)置了 AE_TIME_EVENTS ,則執(zhí)行時(shí)間事件
// 如果flags設(shè)置了 AE_DONT_WAIT ,那么函數(shù)處理完事件后直接返回,不阻塞等待
// 函數(shù)返回執(zhí)行的事件個(gè)數(shù)
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents;
// 如果什么事件都沒(méi)有設(shè)置則直接返回
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
// 請(qǐng)注意,既然我們要處理時(shí)間事件,即使沒(méi)有要處理的文件事件,我們?nèi)砸{(diào)用select(),以便在下一次事件準(zhǔn)備啟動(dòng)之前進(jìn)行休眠
// 當(dāng)前還沒(méi)有要處理的文件事件,或者設(shè)置了時(shí)間事件但是沒(méi)有設(shè)置不阻塞標(biāo)識(shí)
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
// 如果設(shè)置了時(shí)間事件而沒(méi)有設(shè)置不阻塞標(biāo)識(shí)
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
// 獲取最近到時(shí)的時(shí)間事件
shortest = aeSearchNearestTimer(eventLoop);
// 獲取到了最早到時(shí)的時(shí)間事件
if (shortest) {
long now_sec, now_ms;
// 獲取當(dāng)前時(shí)間
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
// 等待該時(shí)間事件到時(shí)所需要的時(shí)長(zhǎng)
long long ms =
(shortest->when_sec - now_sec)*1000 +
shortest->when_ms - now_ms;
// 如果沒(méi)到時(shí)
if (ms > 0) {
// 保存時(shí)長(zhǎng)到tvp中
tvp->tv_sec = ms/1000;
tvp->tv_usec = (ms % 1000)*1000;
// 如果已經(jīng)到時(shí),則將tvp的時(shí)間設(shè)置為0
} else {
tvp->tv_sec = 0;
tvp->tv_usec = 0;
}
// 沒(méi)有獲取到了最早到時(shí)的時(shí)間事件,時(shí)間事件鏈表為空
} else {
// 如果設(shè)置了不阻塞標(biāo)識(shí)
if (flags & AE_DONT_WAIT) {
// 將tvp的時(shí)間設(shè)置為0,就不會(huì)阻塞
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
// 阻塞到第一個(gè)時(shí)間事件的到來(lái)
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
}
// 等待所監(jiān)聽(tīng)文件描述符上有事件發(fā)生
// 如果tvp為NULL,則阻塞在此,否則等待tvp設(shè)置阻塞的時(shí)間,就會(huì)有時(shí)間事件到時(shí)
// 返回了就緒文件事件的個(gè)數(shù)
numevents = aeApiPoll(eventLoop, tvp);
// 遍歷就緒文件事件表
for (j = 0; j < numevents; j++) {
// 獲取就緒文件事件的地址
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
// 獲取就緒文件事件的類型,文件描述符
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int rfired = 0;
// 如果是文件可讀事件發(fā)生
if (fe->mask & mask & AE_READABLE) {
// 設(shè)置讀事件標(biāo)識(shí) 且 調(diào)用讀事件方法處理讀事件
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
}
// 如果是文件可寫事件發(fā)生
if (fe->mask & mask & AE_WRITABLE) {
// 讀寫事件的執(zhí)行發(fā)法不同,則執(zhí)行寫事件,避免重復(fù)執(zhí)行相同的方法
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
}
processed++; //執(zhí)行的事件次數(shù)加1
}
}
/* Check time events */
// 執(zhí)行時(shí)間事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}
Redis服務(wù)器在沒(méi)有被事件觸發(fā)時(shí),如果沒(méi)有設(shè)置AE_DONT_WAIT標(biāo)識(shí),就會(huì)開始阻塞等待. 但是它不會(huì)死等待,因?yàn)檫€需要處理時(shí)間事件,所以在調(diào)用aeApiPoll進(jìn)行監(jiān)聽(tīng)之前,會(huì)先從時(shí)間事件表中獲取一個(gè)最近到達(dá)的時(shí)間,根據(jù)需要等待的時(shí)間構(gòu)建一個(gè)struct timeval tv, *tvp結(jié)構(gòu)的變量,這個(gè)變量保存著服務(wù)器阻塞等待文件事件的最長(zhǎng)時(shí)間,一旦時(shí)間到達(dá)而沒(méi)有觸發(fā)文件事件aeApiPoll函數(shù)就會(huì)停止阻塞,進(jìn)而調(diào)用processTimeEvents函數(shù)處理時(shí)間事件.
如果在阻塞等待的最長(zhǎng)時(shí)間之間,觸發(fā)了文件事件,就會(huì)先執(zhí)行文件事件,后執(zhí)行時(shí)間事件,因此處理時(shí)間事件通常比預(yù)設(shè)的會(huì)晚一點(diǎn).
而執(zhí)行文件事件rfileProc和wfileProc也是調(diào)用了回調(diào)函數(shù),Redis將文件事件的處理分為了好幾種,用于處理不同的網(wǎng)絡(luò)通信需求:
acceptTcpHandler:用于acceptclient的connect.acceptUnixHandler:用于acceptclient的本地connect.sendReplyToClient:用于向client發(fā)送命令回復(fù).readQueryFromClient:用于讀入client發(fā)送的請(qǐng)求.
然后我們來(lái)看一下獲取最快達(dá)到時(shí)間事件的函數(shù)aeSearchNearestTimer實(shí)現(xiàn):
// 尋找第一個(gè)快到時(shí)的時(shí)間事件
// 這個(gè)操作是有用的知道有多少時(shí)間可以選擇該事件設(shè)置為不用推遲任何事件的睡眠中。
// 如果事件鏈表沒(méi)有時(shí)間將返回NULL。
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
{
// 時(shí)間事件頭節(jié)點(diǎn)地址
aeTimeEvent *te = eventLoop->timeEventHead;
aeTimeEvent *nearest = NULL;
// 遍歷所有的時(shí)間事件
while(te) {
// 尋找第一個(gè)快到時(shí)的時(shí)間事件,保存到nearest中
if (!nearest || te->when_sec < nearest->when_sec ||
(te->when_sec == nearest->when_sec &&
te->when_ms < nearest->when_ms))
nearest = te;
te = te->next;
}
return nearest;
}
該函數(shù)就是遍歷時(shí)間事件鏈表,然后找到最小值.
我們重點(diǎn)看執(zhí)行時(shí)間事件的函數(shù)processTimeEvents函數(shù)的實(shí)現(xiàn):
// 執(zhí)行時(shí)間事件
static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
aeTimeEvent *te, *prev;
long long maxId;
time_t now = time(NULL);
// 這里嘗試發(fā)現(xiàn)時(shí)間混亂的情況,上一次處理事件的時(shí)間比當(dāng)前時(shí)間還要大
// 重置最近一次處理事件的時(shí)間
if (now < eventLoop->lastTime) {
te = eventLoop->timeEventHead;
while(te) {
te->when_sec = 0;
te = te->next;
}
}
// 設(shè)置上一次時(shí)間事件處理的時(shí)間為當(dāng)前時(shí)間
eventLoop->lastTime = now;
prev = NULL;
te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1; //當(dāng)前時(shí)間事件表中的最大ID
// 遍歷時(shí)間事件鏈表
while(te) {
long now_sec, now_ms;
long long id;
/* Remove events scheduled for deletion. */
// 如果時(shí)間事件已被刪除了
if (te->id == AE_DELETED_EVENT_ID) {
aeTimeEvent *next = te->next;
// 從事件鏈表中刪除事件的節(jié)點(diǎn)
if (prev == NULL)
eventLoop->timeEventHead = te->next;
else
prev->next = te->next;
// 調(diào)用時(shí)間事件終結(jié)方法清除該事件
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
te = next;
continue;
}
// 確保我們不處理在此迭代中由時(shí)間事件創(chuàng)建的時(shí)間事件. 請(qǐng)注意,此檢查目前無(wú)效:我們總是在頭節(jié)點(diǎn)添加新的計(jì)時(shí)器,但是如果我們更改實(shí)施細(xì)節(jié),則該檢查可能會(huì)再次有用:我們將其保留在未來(lái)的防御
if (te->id > maxId) {
te = te->next;
continue;
}
// 獲取當(dāng)前時(shí)間
aeGetTime(&now_sec, &now_ms);
// 找到已經(jīng)到時(shí)的時(shí)間事件
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
int retval;
id = te->id;
// 調(diào)用時(shí)間事件處理方法
retval = te->timeProc(eventLoop, id, te->clientData);
// 時(shí)間事件次數(shù)加1
processed++;
// 如果不是定時(shí)事件,則繼續(xù)設(shè)置它的到時(shí)時(shí)間
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
// 如果是定時(shí)時(shí)間,則retval為-1,則將其時(shí)間事件刪除,惰性刪除
} else {
te->id = AE_DELETED_EVENT_ID;
}
}
// 更新前驅(qū)節(jié)點(diǎn)指針和后繼節(jié)點(diǎn)指針
prev = te;
te = te->next;
}
return processed; //返回執(zhí)行事件的次數(shù)
}
如果時(shí)間事件不存在,則就調(diào)用finalizerProc指向的回調(diào)函數(shù),刪除當(dāng)前的時(shí)間事件. 如果存在,就調(diào)用timeProc指向的回調(diào)函數(shù)處理時(shí)間事件. Redis的時(shí)間事件分為兩類:
- 定時(shí)事件:讓一段程序在指定的時(shí)間后執(zhí)行一次.
- 周期性事件:讓一段程序每隔指定的時(shí)間后執(zhí)行一次.
如果當(dāng)前的時(shí)間事件是周期性,那么就會(huì)在將時(shí)間周期添加到周期事件的到時(shí)時(shí)間中. 如果是定時(shí)事件,則將該時(shí)間事件刪除.
參考資料:
《Redis設(shè)計(jì)與實(shí)現(xiàn)》
以上就是Redis源碼設(shè)計(jì)剖析之事件處理示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Redis事件處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis偶發(fā)連接失敗案例實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于Redis偶發(fā)連接失敗的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使Redis具有一定的參考學(xué)習(xí)價(jià)值,用需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
redis的hGetAll函數(shù)的性能問(wèn)題(記Redis那坑人的HGETALL)
這篇文章主要介紹了redis的hGetAll函數(shù)的性能問(wèn)題,需要的朋友可以參考下2016-02-02
Redis 對(duì)過(guò)期數(shù)據(jù)的處理方法
這篇文章主要介紹了Redis 對(duì)過(guò)期數(shù)據(jù)的處理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
詳解Redis中的簡(jiǎn)單動(dòng)態(tài)字符串和C字符串的區(qū)別
簡(jiǎn)單動(dòng)態(tài)字符串(SDS)和?C?字符串在實(shí)現(xiàn)和特性上存在一些區(qū)別,這些區(qū)別使得?SDS?更適合作為?Redis?中字符串對(duì)象的內(nèi)部表示,本文給大家介紹一下Redis中的簡(jiǎn)單動(dòng)態(tài)字符串和C字符串的區(qū)別,需要的朋友可以參考下2023-12-12
Redis+Caffeine實(shí)現(xiàn)分布式二級(jí)緩存組件實(shí)戰(zhàn)教程
這篇文章主要介紹了Redis+Caffeine實(shí)現(xiàn)分布式二級(jí)緩存組件實(shí)戰(zhàn)教程,介紹了分布式二級(jí)緩存的優(yōu)勢(shì),使用組件的方法,通過(guò)示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
Redis簡(jiǎn)易延時(shí)隊(duì)列的實(shí)現(xiàn)示例
在實(shí)際的業(yè)務(wù)場(chǎng)景中,經(jīng)常會(huì)遇到需要延時(shí)處理的業(yè)務(wù),本文就來(lái)介紹有下Redis簡(jiǎn)易延時(shí)隊(duì)列的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12

