IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    《Redis官方文档》Redis事件库

    cndpzc发表于 2016-01-25 12:27:38
    love 0

    原文链接 译者:cndpzc

    Redis实现了自己的事件库,代码在ae.c中。想要理解Redis事件库的工作原理,最好的方法就是去理解Redis如何使用它。

    事件循环初始化

    redis.c中的initServer函数初始化了redisServer结构体变量的众多成员,其中一个就是Redis事件循环(event loop)el:

    aeEventLoop *el
    

    initServer调用aeCreateEventLoop(定义在ae.c)初始化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; /* 这是polling API使用的专有数据 */
        aeBeforeSleepProc *beforesleep;
    } aeEventLoop;
    

    aeCreateEventLoop

    aeCreateEventLoop首先为aeEventLoop结构体分配内存,然后调用ae_epoll.c:aeApiCreate。
    aeApiCreate分配aeApiState的空间,它有两个成员:epfd保存epoll_create调用返回的epoll文件描述符,events是Linux epoll库中定义的epoll_event结构类型。后面会再介绍events的使用。
    接下来是ae.c:aeCreateTimeEvent,但是在那之前,initServer会先调用anet.c:anetTcpServer创建一个监听描述符(listening descriptor),默认监听6379端口。返回的监听描述符保存在server.fd。

    aeCreateTimeEvent

    aeCreateTimeEvent接收如下参数:

    • eventLoop:即redis.c中的 server.el。
    • milliseconds:从当前时间开始距离定时器过期的毫秒数。
    • 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创建的一个定时事件(译者注:即前面调用ae.c:aeCreateTimeEvent使用的回调redis.c:serverCron)。
    请记住,aeCreateTimeEvent创建的定时事件很可能已经过期了,因为过期时间只有1毫秒。定时器过期后,timeval结构体的tvp变量会把成员变量秒和毫秒都重置为0。
    tvp结构体变量和事件循环变量作为参数传给了ae_epoll.c:aeApiPoll。
    aeApiPoll函数在epoll描述符上调用了epoll_wait,然后用下面内容填充 eventLoop->fired数组。

    • fd:准备好做读/写操作的描述符,操作类型取决于mask值。
    • mask:标识读/写事件可以在对应描述符上执行。(译者注:有读事件mask |= AE_READABLE,有写事件mask |= AE_WRITABLE)

    aeApiPoll返回已就绪事件的个数。来看个实际的例子,假设有客户端发起了连接请求,那么aeApiPoll将会注意到,使用监听描述符填充eventLoop->fired数组的描述符成员,把mask设为AE_READABLE。
    现在,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循环会在下次迭代中继续处理定时事件。
    就这些了。

    原创文章,转载请注明: 转载自并发编程网 – ifeve.com

    本文链接地址: 《Redis官方文档》Redis事件库



沪ICP备19023445号-2号
友情链接