一 Redis 事件驱动
作为一个网络数据库服务器,不可置否redis也用到了事件驱动的编程模型来实现网络通讯,不像Memcached采用libevent实现网络通讯,redis拒绝了庞大的libevent,尽管libev已经比较轻量,但是仍然比不上只有不到几百行代码的ae事件驱动库。
通常,事件驱动库的组成部分包括以下几个部分:
1)事件,一般由外在因素触发,比如有网络数据到达;
2)事件处理函数,事情发生以后要靠它处理;
3)事件与处理函数之间的映射关系,将上述两种概念联系起来;
4)循环监控,基于事件驱动的程序一般主体是个循环,在每一遍循环中检查发生了哪些事儿,然后调用相应的处理函数;
二 Redis事件驱动的实现
2.1 初始化事件库结构体
在redis.c中定义了initServer函数用来初始化redisServer结构体中的一些字段。其中一个字段就是Redis事件循环 el:
// 事件状态
aeEventLoop *el;
initServer函数通过调用在ae.c文件中定义的aeCreateEventLoop函数初始化server.el字段。aeEventLoop的定义如下:
/* State of an event based program
*
* 事件处理器的状态
*/
typedef struct aeEventLoop {
// 目前已注册的最大描述符
int maxfd; /* highest file descriptor currently registered */
// 目前已追踪的最大描述符
int setsize; /* max number of file descriptors tracked */
// 用于生成时间事件 id
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;
} aeEventLoop;
aeCreateEventLoop
aeCreateEventLoop函数首先分配aeEventLoop结构体的空间,然后调用ae_epoll.c文件中的 aeApiCreate函数。aeApiCreate函数分配aeApiState结构的空间,其中的两个字段:epfd 存放从epoll_create函数返回的epoll文件描述符;event字段是一个Linux epoll库定义的epoll_event结构类型。event字段的用处将会在后续说明。
2.2 处理监听描述符
接着initServer函数调用listenToPort函数,listenToPort函数调用anet.c文件中 anetTcpServer函数,该函数返回一个监听的文件描述符(listening descriptor)_。这个描述符默认是在*6379端口*监听。返回的_监听的文件描述符_存放在inserver.ipfd字段中。
int listenToPort(int port, int *fds, int *count) {
int j;
/* Force binding of 0.0.0.0 if no bind address is specified, always
* entering the loop if j == 0. */
if (server.bindaddr_count == 0) server.bindaddr[0] = NULL;
for (j = 0; j < server.bindaddr_count || j == 0; j++) {
if (server.bindaddr[j] == NULL) {
/* Bind * for both IPv6 and IPv4, we enter here only if
* server.bindaddr_count == 0. */
fds[*count] = anetTcp6Server(server.neterr,port,NULL,
server.tcp_backlog);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
(*count)++;
}
fds[*count] = anetTcpServer(server.neterr,port,NULL,
server.tcp_backlog);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
(*count)++;
}
/* Exit the loop if we were able to bind * on IPv4 or IPv6,
* otherwise fds[*count] will be ANET_ERR and we'll print an
* error and return to the caller with an error. */
if (*count) break;
} else if (strchr(server.bindaddr[j],':')) {
/* Bind IPv6 address. */
fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
server.tcp_backlog);
} else {
/* Bind IPv4 address. */
fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
server.tcp_backlog);
}
if (fds[*count] == ANET_ERR) {
redisLog(REDIS_WARNING,
"Creating Server TCP listening socket %s:%d: %s",
server.bindaddr[j] ? server.bindaddr[j] : "*",
port, server.neterr);
return REDIS_ERR;
}
anetNonBlock(NULL,fds[*count]);
(*count)++;
}
return REDIS_OK;
}
2.3 创建时间事件
initServer函数调用aeCreateTimeEvent函数来添加一个计时事件到server.el的timeEventHead字段 中。timeEventHead是一个指向计时事件的链表。redis.c文件中initServer函数调用aeCreateTimeEvent函数形 式如下:
aeCreateTimeEvent(server.el /*eventLoop*/, 1 /*milliseconds*/, serverCron /*proc*/, NULL /*clientData*/, NULL /*finalizerProc*/);
redis.c文件中的serverCron会进行一些列的操作来保证Redis正常运行。
2.4 创建文件事件
调用aeCreateFileEvent函数的目的是执行epoll_ctl系统调用,以便将由anetTcpServer函数创建的监听描述符加入到EPOLLIN事件队列中。同时将它和aeCreateEventLoop函数调用生成的epoll描述符相关连。
下边详细解释了当initServer调用aeCreateFileEvent函数时的工作,initServer传递接下来的参数给aeCreateFileEvent函数:
server.el:aeCreateEventLoop建立的事件循环。epoll描述符从server.el中获得。
server.fd:负责监听的描述符,同时作为访问相关的文件事件结构体的索引。
AE_READABLE:标志server.fd必须被监视EPOLLIN
事件。
acceptHandler:当监视的事件到达时要执行的函数。函数指针存储在eventLoop->events[server.fd]->rfileProc。
/*
* 根据 mask 参数的值,监听 fd 文件的状态,
* 当 fd 可用时,执行 proc 函数
*/
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
if (fd >= eventLoop->setsize) return AE_ERR;
// 取出文件事件结构
aeFileEvent *fe = &eventLoop->events[fd];
// 监听指定 fd 的指定事件
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
return AE_ERR;
// 设置文件事件类型,以及事件的处理器
fe->mask |= mask;
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
// 私有数据
fe->clientData = clientData;
// 如果有需要,更新事件处理器的最大 fd
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
2.5 运行事件处理器
最后redis启动事件处理器,也就是调用aeMain函数,该函数里面就是一个while循环,在一定条件下不断调用aeProcessEvents函数,aeProcessEvents函数在一个循环周期内处理所有就绪的文件描述符和符合要求的时间时间。
/* Process every pending time event, then every pending file event
* (that may be registered by time event callbacks just processed).
*
* 处理所有已到达的时间事件,以及所有已就绪的文件事件。
*
* Without special flags the function sleeps until some file event
* fires, or when the next time event occurs (if any).
*
* 如果不传入特殊 flags 的话,那么函数睡眠直到文件事件就绪,
* 或者下个时间事件到达(如果有的话)。
*
* If flags is 0, the function does nothing and returns.
* 如果 flags 为 0 ,那么函数不作动作,直接返回。
*
* if flags has AE_ALL_EVENTS set, all the kind of events are processed.
* 如果 flags 包含 AE_ALL_EVENTS ,所有类型的事件都会被处理。
*
* if flags has AE_FILE_EVENTS set, file events are processed.
* 如果 flags 包含 AE_FILE_EVENTS ,那么处理文件事件。
*
* if flags has AE_TIME_EVENTS set, time events are processed.
* 如果 flags 包含 AE_TIME_EVENTS ,那么处理时间事件。
*
* if flags has AE_DONT_WAIT set the function returns ASAP until all
* the events that's possible to process without to wait are processed.
* 如果 flags 包含 AE_DONT_WAIT ,
* 那么函数在处理完所有不许阻塞的事件之后,即刻返回。
*
* The function returns the number of events processed.
* 函数的返回值为已处理事件的数量
*/
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 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. */
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
// 获取最近的时间事件
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
// 如果时间事件存在的话
// 那么根据最近可执行时间事件和现在时间的时间差来决定文件事件的阻塞时间
long now_sec, now_ms;
/* Calculate the time missing for the nearest
* timer to fire. */
// 计算距今最近的时间事件还要多久才能达到
// 并将该时间距保存在 tv 结构中
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
tvp->tv_sec = shortest->when_sec - now_sec;
if (shortest->when_ms < now_ms) {
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
tvp->tv_sec --;
} else {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
// 时间差小于 0 ,说明事件已经可以执行了,将秒和毫秒设为 0 (不阻塞)
if (tvp->tv_sec < 0) tvp->tv_sec = 0;
if (tvp->tv_usec < 0) tvp->tv_usec = 0;
} else {
// 执行到这一步,说明没有时间事件
// 那么根据 AE_DONT_WAIT 是否设置来决定是否阻塞,以及阻塞的时间长度
/* 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 */
if (flags & AE_DONT_WAIT) {
// 设置文件事件不阻塞
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
// 文件事件可以阻塞直到有事件到达为止
tvp = NULL; /* wait forever */
}
}
// 处理文件事件,阻塞时间由 tvp 决定
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;
/* 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. */
// 读事件
if (fe->mask & mask & AE_READABLE) {
// rfired 确保读/写事件只能执行其中一个
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++;
}
}
/* Check time events */
// 执行时间事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}