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);
}