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

posted @ 2022-02-06 17:15  macguz  阅读(164)  评论(0编辑  收藏  举报