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

来源:互联网 发布:mac osx 切换root 编辑:程序博客网 时间:2024/06/10 18:12

事件

  之前在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 设计与实现(第二版)

0 0
原创粉丝点击