Redis AE 异步事件模块
来源:互联网 发布:淘宝钓鱼网站举报 编辑:程序博客网 时间:2024/06/05 05:49
首先想一个问题,为何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需要递增,
Redis在封装事件的处理采用了Reactor模式,添加了定时事件的处理。
Redis处理事件是单进程单线程的,而经典Reator模式对事件是串行处理的。
即如果有一个事件阻塞过久的话会导致整个Redis被阻塞。
下面来简要分析一下Redis AE事件处理模型。
从代码中可以看到它主要支持了epoll、select、kqueue、以及基于Solaris的event ports。
主要提供了对两种类型的事件驱动:
1、IO事件(文件事件),包括有IO的读事件和写事件。
2、定时器事件,包括有一次性定时器和循环定时器。
1、 aeCreateEventLoop
底层epoll多路复用初始化,然后存放在aeEventLoop中 void * 类型的apidata,隐藏了底层的实现。
这里将最大文件描述符作为参数setSize、后面创建的eventLoop->events、eventLoop->fired都是以此
来创建、即用文件描述符作为其索引、以最大内存 sizeof(aeFiredEvent) + sizeof(aeFileEvent) 40字节
乘以总的fd数之内存换取o(1)查找效率是值得的。
3、aeProcessEvents
这个是核心部分,通过epoll_wait将事件分离出来,从而保存到fired中,对于语句
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
通过触发事件的 fd 在events中直接映射找到与事件关联的结构体,从而实现事件分派。
Reactor的核心是实现了事件的分离分派。
总结如下:
1. Reactor模式,串行处理事件
2. 具有定时事件功能(但是不能过多,因为是使用链表实现的)、O(N)复杂度
3. 优先处理读事件
一般想法: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 0
- Redis AE 异步事件模块
- Redis AE模块
- Redis:ae事件模型
- redis的ae事件分析
- 基于redis AE的异步网络框架
- Redis 的处理模型AE 模块
- Redis ae事件驱动源码分析
- redis ae事件驱动的源码分析
- Redis开源代码读书笔记七(ae模块)
- Redis源码分析(二十)--- ae事件驱动
- Redis源码分析(二十)——事件ae
- Redis源码分析笔记5-事件处理组件AE
- 结合redis设计与实现的redis源码学习-14-事件(ae.c/ae_epoll.c)
- redis/ae总结 .
- redis ae.c/acMain()
- ae事件库
- ae事件库
- Redis源代码分析之七:事件驱动库分析——Ae
- Makefile常用万能模板(包括静态链接库、动态链接库、可执行文件)
- Linux服务器添加系统任务---自动备份mysql数据库
- linux 进程间通信-内存映射
- 判断两个字符串重排后是否相同
- 34. Search for a Range
- Redis AE 异步事件模块
- Android之Handler用法总结
- 利用CSS使DOM元素居中
- 开发后期在各个页面中添加友盟统计
- 关于大数的操作
- QT源码阅读——QT与HTML交互编程
- SharedPreferences的基本使用及其封装类SPUtils剖析
- Fast R-CNN
- 5.4.4 关键路径