Redis事件处理库

原文链接:https://redis.io/topics/internals-rediseventlib

Redis执行自己的事件库,其相关实现在文件ae.c中.
理解Redis事件库如何工作的最好办法是理解Redis是如何使用事件库的.

事件循环初始化
文件redis.c中的函数initServer初始化了redisServer结构变量的很多字段,其中就包括事件循环字段el:

aeEventLoop *el

在ae.c文件中的函数initServer调用aeCreateEventLoop来初始化server.el字段.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动态分配aeEventLoop结构的内存空间,然后调用文件ae_epoll.c中的函数aeApiCreate.

函数aeApiCreate使用malloc动态分配aeApiState结构内存空间,该结构有两个字段.epfd字段是系统调用epoll_create的返回值,代表epoll文件描述符.events字段是epoll_event结构类型的变量,该结构定义在linux的epoll库中.该字段的使用稍后将继续说明.

接下来是文件ae.c中的函数aeCreateTimeEvent. 但是在调用该函数之前,函数initServer调用文件anet.c中的函数anetTcpServer,创建并返回监听文件描述符.默认情况下, 该描述符将监听端口6379,返回的监听描述符保存在server.fd字段中.

aeCreateTimeEvent
函数aeCreateTimeEvent接收如下参数:

  • eventLoop: 对应的实参是文件redis.c中的server.el
  • milliseconds: 想对于当前时间, 定时器超时的毫秒数值
  • proc: 函数指针.定时器超时以后,该函数将被调用
  • clientData: 多数情况下为NULL
  • finalizerProc: 函数指针.定时事件从链表中删除前,先执行该函数.

函数initServer调用aeCreateTimeEvent向链表timeEventHead中添加定时事件,该链表结构是server.el中的一个字段.
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系统调用,在监听文件描述符上监听EPOLLIN属性事件,然后关联监听文件描述符和epoll描述符.其中,监听文件描述符是由函数anetTcpServer创建的,epoll描述符是由函数aeCreateEventLoop创建的.

函数aeCreateFileEvent是在由文件redis.c中的函数initServer调用的, 下面是对该函数具体的说明.
函数initServer传递下面的参数到函数aeCreateFileEvent:

  • server.el: 由aeCreateEventLoop创建的事件循环. epoll描述符定义在该结构中.
  • server.fd: 监听文件描述符. 该字段同时作为索引用来存取eventLoop->events中的文件事件结构,文件事件结构中保存着一些额外信息,比如回调函数.
  • AE_READABLE: 监听文件描述符server.fd的EPOLLIN事件的触发条件.
  • acceptHandler: 监听文件描述符的EPOLLIN事件触发以后的执行函数,该函数指针存储在eventLoop->events[server.fd]->rfileProc中.

以上是Redis事件循环的初始化部分.

事件循环处理
事件循环初始化完成以后, 文件redis.c中的main函数调用文件ae.c中的函数aeMain来进行事件循环处理.
文件ae.c中的函数aeMain调用文件ae.c中的函数aeProcessEvents在一个while循环语句中处理激发的定时事件和文件事件.

aeProcessEvents
文件ae.c中的函数aeProcessEvents通过调用函数aeSearchNearestTimer,在事件循环中查找最小超时的定时事件.在当前的实现中,事件循环中只有一个定时事件, 该定时事件由文件ae.c中的函数aeCreateTimeEvent创建.

需要注意的是,函数aeCreateTimeEvent创建的定时事件现在可能已经超时了, 因为其在创建的时候,超时时间设置的是一毫秒.因为定时器已经超时,timeval结构变量tvp的两个字段secondsmicroseconds都被初始化为零.

tvp结构变量和事件循环变量作为输入参数都被传递给文件ae_epoll.c中的aeApiPoll函数.该函数在epoll描述符上进行系统调用epoll_wait,并在事件激发后,填充eventLoop->fired表:

  • fd: 可以进行读写操作的文件描述符,具体可以执行的操作取决于mask的值
  • mask: 掩码,控制激发的文件描述符可以进行何种操作.

函数aeApiPoll返回激发的文件描述符的个数.在Redis工作的时候,如果有客户端连接到Redis服务器,那么函数aeApiPoll将从阻塞状态返回,并填充eventLoop->fired表,记录激发的监听文件描述符和AE_READABLE属性的mask.

由于之前事件循环初始化时, 已经为监听文件描述符的accept系统调用的回调函数注册了函数acceptHandler, accept系统调用返回客户端的连接描述符,因此,函数aeProcessEvents将调用文件redis.c中的函数acceptHandler为连接文件描述符创建一个文件事件,这是通过进一步调用文件ae.c中的函数aeCreateFileEvent来实现的, 就像下面这样:

if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
    readQueryFromClient, c) == AE_ERR) {
    freeClient(c);
    return NULL;
}

其中, 变量credisClient结构变量,c->fd是连接描述符.
接下来,函数aeProcessEvent调用函数processTimeEvents来处理定时事件.

processTimeEvents
该函数遍历定时事件链表eventLoop->timeEventHead.

对于每个超时的定时事件,该函数调用注册的回调函数. 在当前的实现中,Redis为定时事件注册的唯一的回调函数为文件redis.c中的函数serverCron.该回调函数执行完毕以后,其返回值是下一次被再次调用的时间间隔.文件ae.c中的函数aeAddMilliSeconds更新该定时事件的超时时间间隔,在下一次遍历函数aeMainwhile循环时,将再次检查并处理定时事件.

==================================== 分割线 ======================================
注:
在Linux操作系统上, Redis将使用高效率的epoll多路复用IO接口.因为epoll为linux操作独有的实现机制,在其他符合POSIX规范的操作系统上,将使用select多路复用IO接口,select系统调用在所有符合POSIX规范的操作系统上都是可用的. 但是一些非linux操作系统也可能有自己独有的多路复用IO实现机制.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值