*Redis 事件库
Redis 实现了自己的事件库。事件库实现在 ae.c 中。
理解 Redis 事件库工作原理的最佳方式是理解 Redis 如何使用它。
*事件循环初始化
redis.c 中定义的 initServer 函数初始化 redisServer 结构变量的众多字段。其中一个字段是 Redis 事件循环 el:
aeEventLoop *el
initServer 通过调用 ae.c 中定义的 aeCreateEventLoop 来初始化 server.el 字段。aeEventLoop 的定义如下:
typedef struct aeEventLoop
{
int maxfd;
long long timeEventNextId;
aeFileEvent events[AE_SETSIZE]; /* 已注册的事件 */
aeFiredEvent fired[AE_SETSIZE]; /* 触发的事件 */
aeTimeEvent *timeEventHead;
int stop;
void *apidata; /* 用于轮询 API 特定的数据 */
aeBeforeSleepProc *beforesleep;
} aeEventLoop;
*aeCreateEventLoop
aeCreateEventLoop 首先通过 malloc 分配 aeEventLoop 结构,然后调用 ae_epoll.c:aeApiCreate。
aeApiCreate 通过 malloc 分配 aeApiState,它有两个字段——epfd,保存由 epoll_create 调用返回的 epoll 文件描述符,以及 events,其类型为 Linux epoll 库定义的 struct epoll_event。events 字段的用途将在后面描述。
接下来是 ae.c:aeCreateTimeEvent。但在那之前,initServer 调用 anet.c:anetTcpServer 创建并返回一个 监听描述符。该描述符默认监听 6379 端口。返回的 监听描述符 存储在 server.fd 字段中。
*aeCreateTimeEvent
aeCreateTimeEvent 接受以下参数:
eventLoop:在redis.c中这是server.elmilliseconds:从当前时间起计时器到期所需的毫秒数。proc:函数指针。存储计时器到期后必须调用的函数地址。clientData:大部分是NULL。finalizerProc:指向必须在定时事件从定时事件列表中移除之前调用的函数的指针。
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 正常运行的操作。
*aeCreateFileEvent
aeCreateFileEvent 函数的本质是执行 epoll_ctl 系统调用,该系统调用为 anetTcpServer 创建的 监听描述符 上的 EPOLLIN 事件添加监视,并将其与通过调用 aeCreateEventLoop 创建的 epoll 描述符关联起来。
以下是当从 redis.c:initServer 调用时,aeCreateFileEvent 具体所做操作的解释。
initServer 向 aeCreateFileEvent 传递以下参数:
server.el:由aeCreateEventLoop创建的事件循环。epoll描述符从server.el获取。server.fd:监听描述符,也用作索引从eventLoop->events表中访问相关的文件事件结构并存储额外信息,如回调函数。AE_READABLE:表示必须为server.fd监视EPOLLIN事件。acceptHandler:当被监视的事件准备好时必须执行的函数。此函数指针存储在eventLoop->events[server.fd]->rfileProc中。
这完成了 Redis 事件循环的初始化。
*事件循环处理
redis.c:main 调用的 ae.c:aeMain 负责处理上一阶段初始化的事件循环。
ae.c:aeMain 在 while 循环中调用 ae.c:aeProcessEvents,处理挂起的时间和文件事件。
*aeProcessEvents
ae.c:aeProcessEvents 通过调用 ae.c:aeSearchNearestTimer 在事件循环上查找将在最短时间内挂起的时间事件。在我们的例子中,事件循环中只有一个由 ae.c:aeCreateTimeEvent 创建的计时器事件。
请记住,由 aeCreateTimeEvent 创建的计时器事件可能已经到期,因为它的到期时间是一毫秒。由于计时器已经到期,tvp timeval 结构变量的秒和微秒字段被初始化为零。
tvp 结构变量与事件循环变量一起被传递给 ae_epoll.c:aeApiPoll。
aeApiPoll 函数对 epoll 描述符执行 epoll_wait,并用详细信息填充 eventLoop->fired 表:
fd:现在准备好根据掩码值进行读/写操作的描述符。mask:现在可以在相应描述符上执行的读/写事件。
aeApiPoll 返回准备好进行操作的此类文件事件的数量。现在结合上下文来说,如果有任何客户端请求连接,那么 aeApiPoll 会注意到它,并用描述符为 监听描述符、掩码为 AE_READABLE 的条目填充 eventLoop->fired 表。
现在,aeProcessEvents 调用注册为回调的 redis.c:acceptHandler。acceptHandler 在 监听描述符 上执行 accept,返回与客户端的 连接描述符。redis.c:createClient 通过调用 ae.c:aeCreateFileEvent 在 连接描述符 上添加一个文件事件,如下所示:
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
readQueryFromClient, c) == AE_ERR) {
freeClient(c);
return NULL;
}
c 是 redisClient 结构变量,c->fd 是连接描述符。
接下来 ae.c:aeProcessEvent 调用 ae.c:processTimeEvents
*processTimeEvents
ae.processTimeEvents 遍历从 eventLoop->timeEventHead 开始的时间事件列表。
对于每个已经到期的时间事件,processTimeEvents 调用注册的回调。在这种情况下,它调用唯一注册的定时事件回调,即 redis.c:serverCron。回调返回必须以毫秒为单位再次调用该回调的时间。此更改通过调用 ae.c:aeAddMilliSeconds 记录,并将在 ae.c:aeMain while 循环的下一次迭代中处理。
就这些。