redis源码之事件驱动(四)
简介
redis的事件分为两种:
- 定时事件(如定时任务),定时任务事件是通过将定时任务插入到一个任务链表中,定时遍历链表检查是否有到时间的任务需要处理。
- 文件句柄事件(如网络socket可读或可写),文件句柄事件的监听和处理是通过epoll系统调用实现的。
事件对象
基础结构
typedef struct aeEventLoop {
int maxfd; /* highest file descriptor currently registered */
int setsize; /* max number of file descriptors tracked */
long long timeEventNextId;
// 记录了文件句柄对应可读或可写事件的处理函数
aeFileEvent *events; /* Registered events */
// 记录了文件句柄
aeFiredEvent *fired; /* Fired events */
// 所有的事件事件会记录在timeEventHead链表中
aeTimeEvent *timeEventHead;
int stop;
void *apidata; /* This is used for polling API specific data */
// epoll会进入睡眠等待,epoll前调用的函数在beforesleep中
aeBeforeSleepProc *beforesleep;
// epoll后调用
aeBeforeSleepProc *aftersleep;
int flags;
} aeEventLoop;
/* File event structure */
typedef struct aeFileEvent {
int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
// 有可读事件的时候,调用rfileProc函数
aeFileProc *rfileProc;
aeFileProc *wfileProc;
void *clientData;
} aeFileEvent;
/* Time event structure */
typedef struct aeTimeEvent {
long long id; /* time event identifier. */
// 到期时间,这里是绝对时间,到期后需要执行timeProc
monotime when;
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
// 指向链表的下一个和上一个节点
struct aeTimeEvent *prev;
struct aeTimeEvent *next;
int refcount; /* refcount to prevent timer events from being
* freed in recursive time event calls. */
} aeTimeEvent;
事件注册
// 设置fd对应的事件处理函数为proc
// eventLoop->events[fd]->rfileProc
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
// 判断fd是否超出eventLoop->events数组大小
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
aeFileEvent *fe = &eventLoop->events[fd];
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
return AE_ERR;
fe->mask |= mask;
// 根据mask设置对应类型的处理函数为proc
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
// clientData有什么用?
fe->clientData = clientData;
// 记录maxfd有什么意义
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
// 将fd添加到epoll监听列表中
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
aeApiState *state = eventLoop->apidata;
struct epoll_event ee = {0}; /* avoid valgrind warning */
/* If the fd was already monitored for some event, we need a MOD
* operation. Otherwise we need an ADD operation. */
int op = eventLoop->events[fd].mask == AE_NONE ?
EPOLL_CTL_ADD : EPOLL_CTL_MOD;
ee.events = 0;
mask |= eventLoop->events[fd].mask; /* Merge old events */
if (mask & AE_READABLE) ee.events |= EPOLLIN;
if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
ee.data.fd = fd;
if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
return 0;
}
// 创建一个时间事件,插入到eventLoop->timeEventHead的最前面
// 处理函数是proc
// 这里没有对when排序,所以判断是否有超时事件,得遍历整个链表
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
long long id = eventLoop->timeEventNextId++;
aeTimeEvent *te;
te = zmalloc(sizeof(*te));
if (te == NULL) return AE_ERR;
te->id = id;
te->when = getMonotonicUs() + milliseconds * 1000;
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
te->prev = NULL;
te->next = eventLoop->timeEventHead;
te->refcount = 0;
if (te->next)
te->next->prev = te;
eventLoop->timeEventHead = te;
return id;
}
事件触发
// redis 服务器进程起始程序
int main(int argc, char **argv) server.c
// 事件处理主函数, 循环调用aeProcessEvents
void aeMain(aeEventLoop *eventLoop) ae.c
// 事件处理逻辑,主要是处理时间到期的事件和文件句柄的可读可写事件
int aeProcessEvents(aeEventLoop *eventLoop, int flags) ae.c
// 处理时间事件
static int processTimeEvents(aeEventLoop *eventLoop)
- 文件事件的处理是通过调用epoll_wait等待事件发生,然后调用对应文件句柄注册的事件处理函数处理事件;
- 定时事件的处理就是遍历链表eventLoop->timeEventHead检查是否有超时的事件发生。
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents;
/* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
/* Note that we want to call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
// 如果要处理时间时间,且没有设置AE_DONT_WAIT
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
struct timeval tv, *tvp;
int64_t usUntilTimer = -1;
// 处理时间事件,没有设置AE_DONT_WAIT, 就计算需要多久才会有过期事件
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
usUntilTimer = usUntilEarliestTimer(eventLoop);
// 时间格式转换, 如果大于0, tv就记录了还需要多久才会有到期事件
if (usUntilTimer >= 0) {
tv.tv_sec = usUntilTimer / 1000000;
tv.tv_usec = usUntilTimer % 1000000;
tvp = &tv;
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout
* to zero */
// 如果设置了AE_DONT_WAIT 直接设置tv为0
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
}
// 如果eventLoop本身设置了AE_DONT_WAIT,表示epoll_wait不阻塞,直接返回
if (eventLoop->flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
}
// 这个调用顺序,是否说明aeApiPoll会阻塞
if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP)
eventLoop->beforesleep(eventLoop);
/* Call the multiplexing API, will return only on timeout or when
* some event fires. */
// epoll_wait的返回结果放入eventLoop->fired
numevents = aeApiPoll(eventLoop, tvp);
/* After sleep callback. */
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
eventLoop->aftersleep(eventLoop);
// 处理fired[n].fd找到对应的事件对象,调用事件对象的处理函数rfileProc或wfileProc
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 fired = 0; /* Number of events fired for current fd. */
/* Normally we execute the readable event first, and the writable
* event later. This is useful as sometimes we may be able
* to serve the reply of a query immediately after processing the
* query.
*
* However if AE_BARRIER is set in the mask, our application is
* asking us to do the reverse: never fire the writable event
* after the readable. In such a case, we invert the calls.
* This is useful when, for instance, we want to do things
* in the beforeSleep() hook, like fsyncing a file to disk,
* before replying to a client. */
// 一般情况是先处理可读事件,然后处理可写事件
// 而inver是表示需要先处理写事件,然后处理读事件
int invert = fe->mask & AE_BARRIER;
/* Note the "fe->mask & mask & ..." code: maybe an already
* processed event removed an element that fired and we still
* didn't processed, so we check if the event is still valid.
*
* Fire the readable event if the call sequence is not
* inverted. */
// 处理读事件
if (!invert && fe->mask & mask & AE_READABLE) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
}
/* Fire the writable event. */
// 处理写事件
if (fe->mask & mask & AE_WRITABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if (invert) {
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
if ((fe->mask & mask & AE_READABLE) &&
(!fired || fe->wfileProc != fe->rfileProc))
{
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
processed++;
}
}
/* Check time events */
// 处理eventLoop->timeEventHead链表中的到期事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}

浙公网安备 33010602011771号