Redis AE 异步事件模块

首先想一个问题,为何Redis比Memcached快呢?
一般想法:Memcached完全基于内存,而Redis具有持久化保存特性,即使是异步的,Redis也不可能比Memcached快。
可实际测试情况基本上是:Redis占绝对优势。


可能原因有二:
1、Libevent: Memcached使用、而Redis没有选用。Libevent为了迎合通用性造成代码庞大及牺牲了在特定平台的不少性能。Redis一直坚持设计小巧并去依赖库的思路。
2、CAS问题:CAS是Memcached中比较方便的一种防止竞争修改资源的方法。
   CAS实现需要为每个cache key设置一个隐藏的cas token,cas相当value版本号,每次set会将token需要递增,

   因此带来CPU和内存的双重开销、但达到单机10G+ cache以及QPS上万之后这些开销就会给双方相对带来一些

   细微性能差别。


Redis在封装事件的处理采用了Reactor模式,添加了定时事件的处理。
Redis处理事件是单进程单线程的,而经典Reator模式对事件是串行处理的。
即如果有一个事件阻塞过久的话会导致整个Redis被阻塞。



下面来简要分析一下Redis AE事件处理模型。


从代码中可以看到它主要支持了epoll、select、kqueue、以及基于Solaris的event ports。
主要提供了对两种类型的事件驱动:
1、IO事件(文件事件),包括有IO的读事件和写事件。
2、定时器事件,包括有一次性定时器和循环定时器。

基本的数据结构:@ae.h


//定义文件事件处理接口(函数指针)

<span style="font-size:18px;">typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);

//时间事件处理接口(函数指针),该函数返回定时的时长
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);

//aeMain中使用,在调用处理事件前调用
typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);</span>

<span style="font-size:18px;">//文件事件结构体
typedef struct aeFileEvent {
    //读或者写,也用于标识该事件结构体是否正在使用
    int mask; /* one of AE_(READABLE|WRITABLE) */
    //读事件的处理函数
    aeFileProc *rfileProc;
    //写事件的处理函数
    aeFileProc *wfileProc;
    //传递给上述两个函数的数据
    void *clientData;
} aeFileEvent;

//时间事件
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;

//这里用于保存已触发的事件
typedef struct aeFiredEvent {
    int fd;
    int mask;
} aeFiredEvent;</span>
<span style="font-size:18px;">/* 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;
    //用于检测系统时间是否变更(判断标准 now<lastTime)
    time_t lastTime;     /* Used to detect system clock skew */
    //注册要使用的文件事件,这里的分离表实现为直接索引,即通过fd来访问,实现事件的分离
    aeFileEvent *events; /* Registered events */
    //已触发的事件
    aeFiredEvent *fired; /* Fired events */
    aeTimeEvent *timeEventHead;
    //停止标志,1表示停止
    int stop;
    //这个是处理底层特定API的数据,对于epoll来说,该结构体包含了epoll fd和epoll_event
    void *apidata; /* This is used for polling API specific data */
    //在调用processEvent前(即如果没有事件则睡眠),调用该处理函数
    aeBeforeSleepProc *beforesleep;
} aeEventLoop;</span>


1、 aeCreateEventLoop

底层epoll多路复用初始化,然后存放在aeEventLoop中 void * 类型的apidata,隐藏了底层的实现。

<span style="font-size:18px;">typedef struct aeApiState {
    int epfd;
    struct epoll_event *events;
} aeApiState;</span>

<span style="font-size:18px;">//ae底层的数据创建以及初始化
static int aeApiCreate(aeEventLoop *eventLoop) {
    aeApiState *state = zmalloc(sizeof(aeApiState));

    if (!state) return -1;
    //创建setsize个epoll_event
    state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);
    if (!state->events) {
        zfree(state);
        return -1;
    }
    state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
    if (state->epfd == -1) {
        zfree(state->events);
        zfree(state);
        return -1;
    }
    eventLoop->apidata = state;
    return 0;
}

//创建事件循环,setsize为最大事件的的个数,对于epoll来说也是epoll_event的个数
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;
    
    //初始化最多setsize个事件
    eventLoop->setsize = setsize;
    eventLoop->lastTime = time(NULL);
    eventLoop->timeEventHead = NULL;
    eventLoop->timeEventNextId = 0;
    eventLoop->stop = 0;
    eventLoop->maxfd = -1;
    eventLoop->beforesleep = NULL;
    
    //这一步为创建底层IO处理的数据,如epoll,创建epoll_event,和epfd
    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;
}</span>

这里将最大文件描述符作为参数setSize、后面创建的eventLoop->events、eventLoop->fired都是以此
来创建、即用文件描述符作为其索引、以最大内存 sizeof(aeFiredEvent) + sizeof(aeFileEvent) 40字节
乘以总的fd数之内存换取o(1)查找效率是值得的。


2、aeCreateFileEvent

对于创建文件事件,需要传入一个该事件对应的处理程序,当事件发生时,会调用对应的回调函数。
这里设计的aeFileEvent结构体就是将事件源(FD),事件,事件处理程序关联起来。

<span style="font-size:18px;">//添加监听的事件,其中如果该fd对应的事件已经存在,则为修改合并旧的事件
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; /* avoid valgrind warning */
    /* If the fd was already monitored for some event, we need a MOD
     * operation. Otherwise we need an ADD operation. */
    //判断fd是否已经添加了事件的监听
    int op = eventLoop->events[fd].mask == AE_NONE ?
            EPOLL_CTL_ADD : EPOLL_CTL_MOD;

    ee.events = 0;
    mask |= eventLoop->events[fd].mask; /* Merge old events */
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.fd = fd;
    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    return 0;
}

//删除指定事件的监听
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; /* avoid valgrind warning */
    int mask = eventLoop->events[fd].mask & (~delmask);

    ee.events = 0;
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.fd = fd;
    if (mask != AE_NONE) {
        epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);
    } else {
        /* Note, Kernel < 2.6.9 requires a non null event pointer even for
         * EPOLL_CTL_DEL. */
        epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee);
    }
}

//创建文件事件,并将该事件注册到eventLoop中
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    
    //直接使用fd来获取FileEvent,来后面分离事件时也采用这种方法(直接索引)
    aeFileEvent *fe = &eventLoop->events[fd];

	//该该事件添加eventLoop中或者修改原来的已有的(保留旧的)
    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;
}</span>

3、aeProcessEvents

这个是核心部分,通过epoll_wait将事件分离出来,从而保存到fired中,对于语句 
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; 
通过触发事件的 fd 在events中直接映射找到与事件关联的结构体,从而实现事件分派。
Reactor的核心是实现了事件的分离分派。

<span style="font-size:18px;">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) {
        int j;

        numevents = retval;
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;

            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
            // 利用fired数组记录触发的事件
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }
    return numevents;
}

//事件处理程序
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;

	//如果有文件事件或者设置了时间事件并且没有设置DONT_WAIT标志
    /* 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;

        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
        	//查找时间最早的时间事件
            shortest = aeSearchNearestTimer(eventLoop);
        if (shortest) {
            long now_sec, now_ms;

            aeGetTime(&now_sec, &now_ms);
            tvp = &tv;

            /* How many milliseconds we need to wait for the next
             * time event to fire? */
            long long ms =
                (shortest->when_sec - now_sec)*1000 +
                shortest->when_ms - now_ms;
			// 找到最早的时间事件与当前时间差值就是epoll wait时间
            if (ms > 0) {
                tvp->tv_sec = ms/1000;
                tvp->tv_usec = (ms % 1000)*1000;
            } else {
                tvp->tv_sec = 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 {
            	//如果没有时间事件则可以阻塞、如果此时加入一个Timer event,啥时候唤醒呢?!
                /* Otherwise we can block */
                tvp = NULL; /* wait forever */
            }
        }

        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;
                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 */
}</span>

总结如下:
1. Reactor模式,串行处理事件
2. 具有定时事件功能(但是不能过多,因为是使用链表实现的)、O(N)复杂度
3. 优先处理读事件


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值