• Redis源码分析事件处理器


    事件处理器:

    ​ Redis采用Reactor模式作为自己的网络事件处理器,可以看作是单线程单Reactor模型。

    一、主要结构体:

    1、事件:

    /* File event structure */
    typedef struct aeFileEvent {
        /* 事件类型:可读or可写 */
        int mask; /* one of AE_(READABLE|WRITABLE) */
        aeFileProc *rfileProc;
        aeFileProc *wfileProc;
        void *clientData;
    } aeFileEvent;
    
    /* Time event structure */
    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;
    
    /* A fired event */
    typedef struct aeFiredEvent {
        int fd;
        int mask;
    } aeFiredEvent;
    
    
    • Redis的事件分为文件事件与时间事件;

    • L5:文件事件处理函数,一个函数指针:

      typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
      
    • L15:时间事件处理函数,函数指针:

      typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
      

    2、 事件循环:

    typedef struct aeEventLoop {
        int maxfd;   /* highest file descriptor currently registered */
        int setsize; /* max number of file descriptors tracked */
        long long timeEventNextId;
        time_t lastTime;     /* Used to detect system clock skew */
        /* 用户层保存的events,不是poller系统调用的events,注意区别 */
        aeFileEvent *events; /* Registered events */
        aeFiredEvent *fired; /* Fired events */
        /* 时间事件列表 */
        aeTimeEvent *timeEventHead;
        int stop;
        void *apidata; /* This is used for polling API specific data */
        aeBeforeSleepProc *beforesleep;
    } aeEventLoop;
    
    • L4:Redis自己实现定时器;
    • L8:注意区别,这里是epoll_wait(2)返回的事件;
    • L10:时间事件被放置在一个无序列表中,由于当前版本(Redis3.0)下Redis服务器只有serverCron一个时间事件,所以这个列表实际上退化为指针,无序也不影响性能;

    二、创建及初始化:

    1、创建事件循环:

    aeEventLoop *aeCreateEventLoop(int setsize) {
        aeEventLoop *eventLoop;
        int i;
    
        if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
        eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
        eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
        if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
        eventLoop->setsize = setsize;
        eventLoop->lastTime = time(NULL);
        eventLoop->timeEventHead = NULL;
        eventLoop->timeEventNextId = 0;
        eventLoop->stop = 0;
        eventLoop->maxfd = -1;
        eventLoop->beforesleep = NULL;
        /* aeApiCreate:系统适配的poller api的初始化
         * Linux下使用epoll,创建epoll api所需要的epollfd和epoll_event数组,并放入apidata中*/
        if (aeApiCreate(eventLoop) == -1) goto err;
        /* Events with mask == AE_NONE are not set. So let's initialize the
         * vector with it. */
        for (i = 0; i < setsize; i++)
            eventLoop->events[i].mask = AE_NONE;
        return eventLoop;
    
    err:
        if (eventLoop) {
            zfree(eventLoop->events);
            zfree(eventLoop->fired);
            zfree(eventLoop);
        }
        return NULL;
    }
    
    • L12:Redis没有使用timefd的api;
    • L18:注意Redis会选择对于该系统适配最佳的poller;

    2、创建文件事件:

    int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
            aeFileProc *proc, void *clientData)
    {
        /* fd不能超过setsize */
        if (fd >= eventLoop->setsize) {
            errno = ERANGE;
            return AE_ERR;
        }
        /* 取出空的aeFileEvent */
        aeFileEvent *fe = &eventLoop->events[fd];
        /* Linux下调用(之后默认Linux)epoll_ctl(2)添加事件 */
        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;
    }
    
    • L12:封装了epoll_ctl_*系统调用;

      注意Redis使用的是LT模式,所以可写事件响应时,回调函数处理结束后必须立刻取消对可写事件的监听,避免busyloop!

    3、创建时间事件:

    long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
            aeTimeProc *proc, void *clientData,
            aeEventFinalizerProc *finalizerProc)
    {
        /* 分配id */
        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;
        /* 新事件插到链表头 */
        te->next = eventLoop->timeEventHead;
        eventLoop->timeEventHead = te;
        return id;
    }
    

    三、开始事件循环:

    • 在Redis main函数中调用aeMain:
    void aeMain(aeEventLoop *eventLoop) {
        eventLoop->stop = 0;
        while (!eventLoop->stop) {
            if (eventLoop->beforesleep != NULL)
                eventLoop->beforesleep(eventLoop);
            aeProcessEvents(eventLoop, AE_ALL_EVENTS);
        }
    }
    
    • L5:每次进入事件循环时还会执行一个beforesleep函数,这个函数会在AOF模式下更新AOF文件。

    1、事件处理函数:

    int aeProcessEvents(aeEventLoop *eventLoop, int flags)
    {
        int processed = 0, numevents;
    
        /* Nothing to do? return ASAP */
        if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
    
        /* Note that we want call select() even if there are no
         * file events to process as long as we want to process time
         * events, in order to sleep until the next time event is ready
         * to fire. */
        if (eventLoop->maxfd != -1 ||
            ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
            int j;
            aeTimeEvent *shortest = NULL;
            struct timeval tv, *tvp;
    
            /* 根据最近的时间事件设置poller调用的阻塞时间 */
            if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
                shortest = aeSearchNearestTimer(eventLoop);
            if (shortest) {
                long now_sec, now_ms;
    
                /* Calculate the time missing for the nearest
                 * timer to fire. */
                aeGetTime(&now_sec, &now_ms);
                tvp = &tv;
                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 we have to check for events but need to return
                 * ASAP because of AE_DONT_WAIT we need to set the timeout
                 * to zero */
                if (flags & AE_DONT_WAIT) {
                    tv.tv_sec = tv.tv_usec = 0;
                    tvp = &tv;
                } else {
                    /* Otherwise we can block */
                    tvp = NULL; /* wait forever */
                }
            }
            /* epoll_wait调用 */
            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 确保读/写事件只能执行其中一个 */
                    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++;
            }
        }
        /* Check time events */
        /* 先处理文件事件,再处理时间事件 */
        if (flags & AE_TIME_EVENTS)
            processed += processTimeEvents(eventLoop);
    
        return processed; /* return the number of processed file/time events */
    }
    
    • L19、L75:以最近时间事件的到达时间作为poller调用阻塞时间,以及先处理文件事件,再处理时间事件的好处是:
      1. 避免长时间阻塞在文件事件的监听上;
      2. 处理完文件事件后最近时间事件刚好或即将触发,这时再处理效率更高;
    • L76:时间事件分为周期性事件和定时事件,周期性事件在执行完后会被重新监听,目前Redis用的就是周期性事件;

    参考:

    ​ 本文基于redis 3.0

  • 相关阅读:
    gcc代码反汇编查看内存分布[1]: gcc
    centos5.5 安装git
    裸机代码(uboot) : clear bss
    互联网协议入门
    git从github下载代码
    linux账户管理(centos)
    jz2440: linux/arch/arm/下面的plat-和mach-
    位置无关码
    【漫画】什么是外部排序?【转】
    快速排序 Vs. 归并排序 Vs. 堆排序——谁才是最强的排序算法
  • 原文地址:https://www.cnblogs.com/macguz/p/15865937.html
Copyright © 2020-2023  润新知