在目前的很多服务中,由于需要持续接受客户端或者用户的输入,所以需要一个事件循环来等待并处理外部事件,这篇文章主要会介绍 Redis 中的事件循环是如何处理事件的。
在文章中,我们会先从 Redis 的实现中分析事件是如何被处理的,然后用更具象化的方式了解服务中的不同模块是如何交流的。
aeEventLoop在分析具体代码之前,先了解一下在事件处理中处于核心部分的 aeEventLoop 到底是什么:

aeEventLoop 在 Redis 就是负责保存待处理文件事件和时间事件的结构体,其中保存大量事件执行的上下文信息,同时持有三个事件数组:
aeFileEvent aeTimeEvent aeFiredEventaeFileEvent 和 aeTimeEvent 中会存储监听的文件事件和时间事件,而最后的 aeFiredEvent 用于存储待处理的文件事件,我们会在后面的章节中介绍它们是如何工作的。
Redis 服务中的 EventLoop在 redis-server 启动时,首先会初始化一些 redis 服务的配置,最后会调用 aeMain 函数陷入 aeEventLoop 循环中,等待外部事件的发生:
int main(int argc, char **argv) { ... aeMain(server.el); }aeMain 函数其实就是一个封装的 while 循环,循环中的代码会一直运行直到 eventLoop 的 stop 被设置为 true :
void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; while (!eventLoop->stop) { if (eventLoop->beforesleep != NULL) eventLoop->beforesleep(eventLoop); aeProcessEvents(eventLoop, AE_ALL_EVENTS); } }它会不停尝试调用 aeProcessEvents 对可能存在的多种事件进行处理,而 aeProcessEvents 就是实际用于处理事件的函数:
int aeProcessEvents(aeEventLoop *eventLoop, int flags) { int processed = 0, numevents; if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; if (eventLoop->maxfd != -1 || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { struct timeval *tvp; #1:计算 I/O 多路复用的等待时间 tvp numevents = aeApiPoll(eventLoop, tvp); for (int 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; if (fe->mask & mask & AE_READABLE) { rfired = 1; fe->rfileProc(eventLoop,fd,fe->clientData,mask); } if (fe->mask & mask & AE_WRITABLE) { if (!rfired || fe->wfileProc != fe->rfileProc) fe->wfileProc(eventLoop,fd,fe->clientData,mask); } processed++; } } if (flags & AE_TIME_EVENTS) processed += processTimeEvents(eventLoop); return processed; }上面的代码省略了 I/O 多路复用函数的等待时间,不过不会影响我们对代码的理解,整个方法大体由两部分代码组成,一部分处理文件事件,另一部分处理时间事件。
Redis 中