redis学习笔记(18)---事件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012658346/article/details/51387265

事件

  之前在redis学习笔记(12)—server基本流程 中,对redis的事件机制已经进行了简单的介绍。
  redis服务器是一个事件驱动程序,server需要处理两类事件:
  1)文件事件:如server与client之间的通信
  2)时间事件:在特定的时间点执行,如serverCron函数

IO复用机制

  redis的IO多路复用的实现都是通过封装select、epoll等IO多路复用函数库实现的,每个IO多路复用函数库都对应一个单独的文件,如ae_select.c、ae_epoll.c、ae_kqueue.c等。
  这些多路复用函数库最终都封装成相同的API,可参见redis服务器模型  

static int aeApiCreate(aeEventLoop *eventLoop);  //创建监听集
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask);  //加入一个监听事件
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask);  //移除一个监听事件
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp);  //调用select/epoll监听事件

  然后在ae.c中,根据具体的运行环境,来选择相应的头文件

#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
    #ifdef HAVE_EPOLL
    #include "ae_epoll.c"
    #else
        #ifdef HAVE_KQUEUE
        #include "ae_kqueue.c"
        #else
        #include "ae_select.c"
        #endif
    #endif
#endif

  这样在运行时,程序就能根据实际运行环境来为API选择对应的实现机制,以达到最高效的处理。
  这里写图片描述

文件事件

创建文件事件

  redis中的文件事件都是通过aeCreateFileEvent来创建的     

/* fd为要监听的描述符,mask为要监听的事件类型,proc为对应的处理函数*/
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    aeFileEvent *fe = &eventLoop->events[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;
    if (fd > eventLoop->maxfd)
        eventLoop->maxfd = fd;
    return AE_OK;
}

  redis中的socket可以分为两类:
  1)监听socket,只需要监听来自客户端的连接请求
  因此只需要注册一个读事件,处理函数为acceptTcpHandler()  

//在initServer()中调用
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL)

  2) 连接socket:与已经建立连接的client通信
  初始时也只需要监听来自client的请求,因此需要注册一个读事件 readQueryFromClient()。
  当server处理了来自client的请求后,需要将操作的结果返回给client,因此此时需要注册一个写事件sendReplyToClient()  

//在createClient中调用
aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c);
//在prepareClientToWrite中调用
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
                sendReplyToClient, c); 

处理文件事件

  redis处理文件事件的基本流程是:
  这里写图片描述
  
  redis处理文件事件和时间事件都是在函数aeProcessEvents中完成的
  由于select、epoll等IO复用机制在一定时间内没有事件发生时,会一直阻塞在那里。
  因此为了不影响后面时间事件的处理,必须在最近的一个时间事件到来之前,完成IO复用机制的调用
  因此首先找到最近一个时间事件,计算距离当前时间的时间差,来作为调用aeApiPoll()的参数。  

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    if (eventLoop->maxfd != -1 ||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { 
        //1、有时间事件时,计算时间差
        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))  
            shortest = aeSearchNearestTimer(eventLoop); //找到最近一个时间事件
        if (shortest) {
            aeGetTime(&now_sec, &now_ms); //得到当前时间
            tvp = &tv;
            //计算时间差tvp
            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;
            }
            if (tvp->tv_sec < 0) tvp->tv_sec = 0;
            if (tvp->tv_usec < 0) tvp->tv_usec = 0;
        } else {
            if (flags & AE_DONT_WAIT) {  //此时不阻塞,立即返回
                tv.tv_sec = tv.tv_usec = 0;
                tvp = &tv;
            } else { //否则可以永远等待
                tvp = NULL; /* wait forever */
            }
        }
        //2、调用IO复用机制,处理IO事件
        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;
            if (fe->mask & mask & AE_READABLE) { //处理读事件
                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++;
        }
    } 
    //3、处理时间事件
    if (flags & AE_TIME_EVENTS)  
        processed += processTimeEvents(eventLoop);
    return processed;
}

时间事件

  所有的时间事件都是保存在链表中的,  

typedef struct aeTimeEvent {
    long long id; /* time event identifier. */
    long when_sec; /* seconds */
    long when_ms; /* milliseconds */
    aeTimeProc *timeProc;  /* 回调函数 */
    aeEventFinalizerProc *finalizerProc;
    void *clientData;
    struct aeTimeEvent *next;
} aeTimeEvent;

  新的时间事件总是插入到链表的头部,所以时间事件按ID逆序排列
  这里写图片描述

创建时间事件

long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,aeTimeProc *proc, void *clientData,aeEventFinalizerProc *finalizerProc)
{
    te = zmalloc(sizeof(*te));

    //设置事件参数
    te->id = id;
    aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); 
    te->timeProc = proc;
    te->finalizerProc = finalizerProc;
    te->clientData = clientData;

    //将事件插入到链表头部
    te->next = eventLoop->timeEventHead; 
    eventLoop->timeEventHead = te;
    return id;
}

处理时间事件

  每次在处理时间事件时,就遍历整个链表,查找所有已经到达的时间事件,然后调用对应的处理函数。   

/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
    eventLoop->lastTime = now; //更新最后一次处理事件

    te = eventLoop->timeEventHead;
    while(te) {
        aeGetTime(&now_sec, &now_ms); //获取当前时间
        if (now_sec > te->when_sec ||  //比较时间
            (now_sec == te->when_sec && now_ms >= te->when_ms))
        {
            id = te->id; 
            //调用时间处理函数
            retval = te->timeProc(eventLoop, id, te->clientData); 
            processed++;

            //判断是否需要循环执行该事件
            if (retval != AE_NOMORE) {//是,更新下次触发时间
                aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
            } else { //否,删除该事件
                aeDeleteTimeEvent(eventLoop, id);
            }
            te = eventLoop->timeEventHead;
        } else {
            te = te->next;
        }
    }
    return processed;
}



本文所引用的源码全部来自Redis3.0.7版本

redis学习参考资料:
https://github.com/huangz1990/redis-3.0-annotated
Redis 设计与实现(第二版)

猜你喜欢

转载自blog.csdn.net/u012658346/article/details/51387265
今日推荐