• Redis源代码分析(二十)--- ae事件驱动


              事件驱动的术语出现更频繁。听起来非常大的,今天我把Redis内部驱动器模型来研究它,奖励的感觉啊。一个ae.c主程序,加4文件的事件类型,让你彻底弄清楚,Redis是怎样处理这些事件的。

    在Redis的事件处理中。用到了epoll,select,kqueue和evport,evport可能大家会陌生很多。前面3个都是很常见的事件,在libevent的事件网络库中也都有出现。

    作者在写这个事件驱动模型的时候。也说了,这仅仅是为了简单的复用了,设计的一个小小的处理模型:

    /* A simple event-driven programming library. Originally I wrote this code
     * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
     * it in form of a library for easy reuse.
     *
     * ae是作者写的一个简单的事件驱动库。后面进行了转化,变得更为简单的复用
    所以不是非常复杂。在了解整个事件驱动的模型前,有先了解一些定义的事件结构体,事件类型总共2个一个FileEvent,TimeEvent:

    /* File event structure */
    /* 文件事件结构体 */
    typedef struct aeFileEvent {
    	//仅仅为读事件或者写事件中的1种
        int mask; /* one of AE_(READABLE|WRITABLE) */
        //读方法
        aeFileProc *rfileProc;
        //写方法
        aeFileProc *wfileProc;
        //客户端数据
        void *clientData;
    } aeFileEvent;
    
    /* Time event structure */
    /* 时间事件结构体 */
    typedef struct aeTimeEvent {
    	//时间事件id
        long long id; /* time event identifier. */
        //时间秒数
        long when_sec; /* seconds */
        //时间毫秒
        long when_ms; /* milliseconds */
        //时间事件中的处理函数
        aeTimeProc *timeProc;
        //被删除的时候将会调用的方法
        aeEventFinalizerProc *finalizerProc;
        //客户端数据
        void *clientData;
        //时间结构体内的下一个结构体
        struct aeTimeEvent *next;
    } aeTimeEvent;
    
    /* A fired event */
    /* fired结构体。用来表示将要被处理的文件事件 */
    typedef struct aeFiredEvent {
    	//文件描写叙述符id
        int fd;
        int mask;
    } aeFiredEvent;
    
    FireEvent仅仅是用来标记要处理的文件Event。

    这些事件都存在于一个aeEventLoop的结构体内:

    /* State of an event based program */
    typedef struct aeEventLoop {
    	//眼下创建的最高的文件描写叙述符
        int maxfd;   /* highest file descriptor currently registered */
        int setsize; /* max number of file descriptors tracked */
        //下一个时间事件id
        long long timeEventNextId;
        time_t lastTime;     /* Used to detect system clock skew */
        //3种事件类型
        aeFileEvent *events; /* Registered events */
        aeFiredEvent *fired; /* Fired events */
        aeTimeEvent *timeEventHead;
        //事件停止标志符
        int stop;
        //这里存放的是event API的数据。包含epoll,select等事件
        void *apidata; /* This is used for polling API specific data */
        aeBeforeSleepProc *beforesleep;
    } aeEventLoop;
    
    在每种事件内部,都有定义对应的处理函数,把函数当做变量一样存在结构体中。以下看下ae.c中的一些API的组成:

    /* Prototypes */
    aeEventLoop *aeCreateEventLoop(int setsize); /* 创建aeEventLoop。内部的fileEvent和Fired事件的个数为setSize个 */
    void aeDeleteEventLoop(aeEventLoop *eventLoop); /* 删除EventLoop。释放对应的事件所占的空间 */
    void aeStop(aeEventLoop *eventLoop); /* 设置eventLoop中的停止属性为1 */
    int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
            aeFileProc *proc, void *clientData); /* 在eventLoop中创建文件事件 */
    void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); /* 删除文件事件 */
    int aeGetFileEvents(aeEventLoop *eventLoop, int fd); //依据文件描写叙述符id,找出文件的属性,是读事件还是写事件
    long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
            aeTimeProc *proc, void *clientData,
            aeEventFinalizerProc *finalizerProc); /* 在eventLoop中加入时间事件,创建的时间为当前时间加上自己传入的时间 */
    int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); //依据时间id。删除时间事件。涉及链表的操作
    int aeProcessEvents(aeEventLoop *eventLoop, int flags); /* 处理eventLoop中的全部类型事件 */
    int aeWait(int fd, int mask, long long milliseconds); /* 让某事件等待 */
    void aeMain(aeEventLoop *eventLoop); /* ae事件运行主程序 */
    char *aeGetApiName(void);
    void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); /* 每次eventLoop事件运行完后又又一次開始运行时调用 */
    int aeGetSetSize(aeEventLoop *eventLoop); /* 获取eventLoop的大小 */
    int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); /* EventLoop又一次调整大小 */
    
    无非涉及一些文件。时间事件的加入,改动等,都是在eventLoop内部的改动,我们来看下最主要,最核心的方法:

    /* ae事件运行主程序 */
    void aeMain(aeEventLoop *eventLoop) {
        eventLoop->stop = 0;
        //假设eventLoop中的stop标志位不为1,就循环处理
        while (!eventLoop->stop) {
        	//每次eventLoop事件运行完后又又一次開始运行时调用
            if (eventLoop->beforesleep != NULL)
                eventLoop->beforesleep(eventLoop);
            //while循环处理全部的evetLoop的事件
            aeProcessEvents(eventLoop, AE_ALL_EVENTS);
        }
    }
    
    道理非常easy通过,while循环,处理eventLoop中的全部类型事件,截取部分processEvents()代码:

     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;
    
    	    /* note the fe->mask & mask & ... code: maybe an already processed
                 * event removed an element that fired and we still didn't
                 * processed, so we check if the event is still valid. */
                if (fe->mask & mask & AE_READABLE) {
                    rfired = 1;
                    //依据掩码计算推断是否为ae读事件。调用时间中的读的处理方法
                    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++;
            }
        }
    ae中创建时间事件都是以当前时间为基准创建的。

    /* 在eventLoop中加入时间事件。创建的时间为当前时间加上自己传入的时间 */
    long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
            aeTimeProc *proc, void *clientData,
            aeEventFinalizerProc *finalizerProc)
    {
        long long id = eventLoop->timeEventNextId++;
        aeTimeEvent *te;
    
        te = zmalloc(sizeof(*te));
        if (te == NULL) return AE_ERR;
        te->id = id;
        aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
        te->timeProc = proc;
        te->finalizerProc = finalizerProc;
        te->clientData = clientData;
        //新加的变为timeEvent的头部
        te->next = eventLoop->timeEventHead;
        eventLoop->timeEventHead = te;
        
        //返回新创建的时间事件的id
        return id;
    }
    
        以下说说怎样调用事件API库里的方法呢。首先隆重介绍什么是epoll,poll。select。kqueu和evport。这些都是一种事件模型。

    select事件的模型 

    (1)创建所关注的事件的描写叙述符集合(fd_set),对于一个描写叙述符。能够关注其上面的读(read)、写(write)、异常(exception)事件,所以通常,要创建三个fd_set。 一个用来收集关注读事件的描写叙述符。一个用来收集关注写事件的描写叙述符。另外一个用来收集关注 异常事件的描写叙述符集合。
    (2)轮询全部fd_set中的每个fd ,检查是否有对应的事件发生。假设有,就进行处理。
       
     poll和上面的差别是能够复用文件描写叙述符,上面对一个文件须要轮询3个文件描写叙述符集合。而poll仅仅须要一个,效率更高
    epoll是poll的升级版本号,把描写叙述符列表交给内核,一旦有事件发生,内核把发生事件的描写叙述符列表通知给进程,这样就避免了轮询整个描写叙述符列表。

    效率极大提高 

    evport这个出现的比較少,大致意思是evport将某一个对象的特定 event 与 Event port 相关联:

    在了解了3种事件模型的原理之后,我们看看ae.c在Redis中是怎样调用的呢,

    //这里存放的是event API的数据,包含epoll,select等事件
        void *apidata; /* This is used for polling API specific data */
    就是上面这个属性。在上面的4种事件中,分别相应着3个文件,分别为ae_poll.c,ae_select.c,可是他们的API结构是类似的。我举当中一个样例,epoll的样例,首先都会有此事件特定的结构体:

    typedef struct aeApiState {
        int epfd;
        struct epoll_event *events;
    } aeApiState;
    还有共同套路的模板方法:

    static int aeApiCreate(aeEventLoop *eventLoop)
    static int aeApiResize(aeEventLoop *eventLoop, int setsize)
    static void aeApiFree(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)
    static char *aeApiName(void)
    在创建的时候赋值到eventloop的API data里面去:

     state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
        if (state->epfd == -1) {
            zfree(state->events);
            zfree(state);
            return -1;
        }
        //最后将state的数据赋值到eventLoop的API data中
        eventLoop->apidata = state;
        return 0;
    在取出事件的poll方法的时候是这些方法的一个区分点:

    static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
        aeApiState *state = eventLoop->apidata;
        int retval, numevents = 0;
    
        retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
                tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
        if (retval > 0) {
    .....
    而在select中的poll方法是这种:

    static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
        aeApiState *state = eventLoop->apidata;
        int retval, j, numevents = 0;
    
        memcpy(&state->_rfds,&state->rfds,sizeof(fd_set));
        memcpy(&state->_wfds,&state->wfds,sizeof(fd_set));
    
        retval = select(eventLoop->maxfd+1,
                    &state->_rfds,&state->_wfds,NULL,tvp);
    ......
    最后都是基于state中的事件和eventLoop之间的转化实现操作。

    传入eventLoop中的信息。传入state的信息。内部得到处理结果最后一场比赛后,。

    就这么简单的调用。

    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    Failed to load resource: the server responded with a status of 413 (Request Entity Too Large)
    钱能解决的都是小事——北漂18年(78)
    mysql 排序
    innodb和myisam表排序
    perl 内部字节字符转换
    encode_json 转换给定的perl数据结构为一个UTF-8编码的 2进制字符串 decode_json把UTF-8字节转换成字符
    perl /g
    perl 循环截取字符串
    eclipse报错:Compilation unit name must end with .java, or one of the registered Java-like exten
    用 Flask 来写个轻博客 (30) — 使用 Flask-Admin 增强文章管理功能
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4716960.html
Copyright © 2020-2023  润新知