事件库
什么是事件库,最初的 Redis 事件库是如何实现的?
Redis 堆栈 | Redis 社区版 |
---|
注意:本文档由 Redis 的创建者 Salvatore Sanfilippo 在 Redis 开发初期(约 2010 年)编写,并不一定反映最新的 Redis 实现。
为什么需要事件库?
让我们通过一系列的问答来弄清楚。
问:您期望网络服务器一直在做什么?
答:观察其侦听的端口上的入站连接并接受它们。
问:调用 accept 会生成一个描述符。我该怎么处理它?
答:保存描述符并对其执行非阻塞读/写作。
Q:为什么读/写必须是非阻塞的?
A: 如果文件作(甚至 Unix 中的套接字也是一个文件)阻塞,例如,当它在文件 I/O作中被阻塞时,服务器如何接受其他连接请求。
问:我想我必须在套接字上执行许多此类非阻塞作,以查看它何时准备就绪。我说得对吗?
答:是的。这就是事件库的作用。现在你明白了。
问:事件库如何完成它们的工作?
答:它们使用作系统的轮询工具以及计时器。
问:那么,是否有任何开源事件库可以执行您刚才描述的作?
答:是的。libevent
和libev
是两个我可以从头顶回忆起的事件库。
问:Redis 是否使用此类开源事件库来处理套接字 I/O?
答:不可以。由于各种原因,Redis 使用自己的事件库。
Redis 事件库
Redis 实现了自己的事件库。事件库在ae.c
.
了解 Redis 事件库工作原理的最佳方法是了解 Redis 如何使用它。
事件循环初始化
initServer
函数定义在redis.c
初始化redisServer
structure 变量。其中一个字段是 Redis 事件循环el
:
aeEventLoop *el
initServer
初始 化server.el
字段aeCreateEventLoop
定义于ae.c
.的定义aeEventLoop
如下所示:
typedef struct aeEventLoop
{
int maxfd;
long long timeEventNextId;
aeFileEvent events[AE_SETSIZE]; /* Registered events */
aeFiredEvent fired[AE_SETSIZE]; /* Fired events */
aeTimeEvent *timeEventHead;
int stop;
void *apidata; /* This is used for polling API specific data */
aeBeforeSleepProc *beforesleep;
} aeEventLoop;
aeCreateEventLoop
aeCreateEventLoop
第一malloc
saeEventLoop
structure 然后调用ae_epoll.c:aeApiCreate
.
aeApiCreate
malloc
saeApiState
,它有两个字段 -epfd
,它持有epoll
由 call from 返回的文件描述符epoll_create
和events
那是struct epoll_event
由 Linux 定义epoll
图书馆。使用events
字段。
接下来是ae.c:aeCreateTimeEvent
.但在此之前initServer
叫anet.c:anetTcpServer
这将创建并返回一个侦听描述符。默认情况下,描述符侦听端口 6379。返回的侦听描述符存储在server.fd
田。
aeCreateTimeEvent
aeCreateTimeEvent
接受以下内容作为参数:
eventLoop
:这是server.el
在redis.c
- milliseconds:计时器过期后从当前时间开始的毫秒数。
proc
:函数指针。存储计时器过期后必须调用的函数的地址。clientData
:主要NULL
.finalizerProc
:指向在从定时事件列表中删除定时事件之前必须调用的函数的指针。
initServer
调用aeCreateTimeEvent
将定时事件添加到timeEventHead
字段为server.el
.timeEventHead
是指向此类定时事件列表的指针。对aeCreateTimeEvent
从redis.c:initServer
函数如下:
aeCreateTimeEvent(server.el /*eventLoop*/, 1 /*milliseconds*/, serverCron /*proc*/, NULL /*clientData*/, NULL /*finalizerProc*/);
redis.c:serverCron
执行许多有助于保持 Redis 正常运行的作。
aeCreateFileEvent
精髓aeCreateFileEvent
函数是执行epoll_ctl
system 调用,该调用为EPOLLIN
侦听描述符上的 event create byanetTcpServer
并将其与epoll
通过调用aeCreateEventLoop
.
以下是对具体内容的解释aeCreateFileEvent
does (当从redis.c:initServer
.
initServer
将以下参数传递给aeCreateFileEvent
:
server.el
:由 创建的事件循环aeCreateEventLoop
.这epoll
descriptor 的server.el
.server.fd
:侦听描述符,也用作索引,用于从eventLoop->events
table 并存储额外信息,如回调函数。AE_READABLE
: 表示server.fd
必须被监视EPOLLIN
事件。acceptHandler
:当正在监视的事件准备就绪时必须执行的函数。此函数指针存储在eventLoop->events[server.fd]->rfileProc
.
这样就完成了 Redis 事件循环的初始化。
事件循环处理
ae.c:aeMain
调用 自redis.c:main
执行处理在上一阶段初始化的事件循环的工作。
ae.c:aeMain
调用ae.c:aeProcessEvents
在处理待处理 Time 和 File 事件的 while 循环中。
aeProcessEvents
ae.c:aeProcessEvents
通过调用ae.c:aeSearchNearestTimer
在事件循环中。在我们的例子中,事件循环中只有一个计时器事件,该事件由ae.c:aeCreateTimeEvent
.
请记住,由aeCreateTimeEvent
现在可能已经过去了,因为它的过期时间为 1 毫秒。由于计时器已过期,因此tvp
timeval
structure 变量初始化为零。
这tvp
structure 变量以及事件循环变量传递给ae_epoll.c:aeApiPoll
.
aeApiPoll
functions 执行epoll_wait
在epoll
描述符并填充eventLoop->fired
包含详细信息的表:
fd
:现在已准备好根据掩码值执行读/写作的描述符。mask
:现在可以对相应描述符执行的读/写事件。
aeApiPoll
返回准备作的此类文件事件的数量。现在将事情放在上下文中,如果任何客户端请求了连接,那么aeApiPoll
会注意到它并填充eventLoop->fired
table 中,其中 Descriptor 的条目是侦听 Descriptor,掩码是AE_READABLE
.
现在aeProcessEvents
调用redis.c:acceptHandler
注册为 callback。acceptHandler
对侦听描述符执行 accept,返回与 Client 端连接的描述符。redis.c:createClient
通过调用ae.c:aeCreateFileEvent
如下所示:
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
readQueryFromClient, c) == AE_ERR) {
freeClient(c);
return NULL;
}
c
是redisClient
structure 变量和c->fd
是连接的描述符。
接下来,ae.c:aeProcessEvent
调用ae.c:processTimeEvents
processTimeEvents
ae.processTimeEvents
迭代从eventLoop->timeEventHead
.
对于已过去的每个定时事件processTimeEvents
调用已注册的回调。在这种情况下,它会调用唯一注册的定时事件回调,即redis.c:serverCron
.回调返回以毫秒为单位的时间,在此时间之后必须再次调用回调。此更改通过调用ae.c:aeAddMilliSeconds
并将在ae.c:aeMain
while 循环。
就这样。