概述 redis 内部有一个小型的事件驱动,它和 libevent 网络库的事件驱动一样,都是依托 I/O 多路复用技术支撑起来的。 利用 I/O 多路复用技术,监听感兴趣的文件 I/O 事件,例如读事件,写事件等,同时也要维护一个以文件描述符为主键,数据为某个预设函数的
概述
redis 内部有一个小型的事件驱动,它和 libevent 网络库的事件驱动一样,都是依托 I/O 多路复用技术支撑起来的。
利用 I/O 多路复用技术,监听感兴趣的文件 I/O 事件,例如读事件,写事件等,同时也要维护一个以文件描述符为主键,数据为某个预设函数的事件表,这里其实就是一个数组或者链表 。当事件触发时,比如某个文件描述符可读,系统会返回文件描述符值,用这个值在事件表中找到相应的数据项,从而实现回调。同样的,定时事件也是可以实现的,因为系统提供的 I/O 多路复用技术中的函数允许我们设定时间值。
上面一段话比
本文来源gao!%daima.com搞$代*!码$网3
较综合,可能需要一些 linux 系统编程和网络编程的基础,但你会看到多数事件驱动程序都是这么实现的(?)。
redis 事件驱动数据结构
redis 事件驱动内部有四个主要的数据结构,分别是:事件循环结构体,文件事件结构体,时间事件结构体和触发事件结构体。
// 文件事件结构体/* File event structure */typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE) */ // 回调函数指针 aeFileProc *rfileProc; aeFileProc *wfileProc; // clientData 参数一般是指向 redisClient 的指针 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; // clientData 参数一般是指向 redisClient 的指针 void *clientData; // 定时事件表采用链表来维护 struct aeTimeEvent *next;} aeTimeEvent;// 触发事件/* A fired event */typedef struct aeFiredEvent { int fd; int mask;} aeFiredEvent;// 事件循环结构体/* 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 + 1 long long timeEventNextId; // 用于系统时间的矫正 time_t lastTime; /* Used to detect system clock skew */ // I/O 事件表 aeFileEvent *events; /* Registered events */ // 被触发的事件 aeFiredEvent *fired; /* Fired events */ // 定时事件表 aeTimeEvent *timeEventHead; // 事件循环结束标识 int stop; // 对于不同的 I/O 多路复用技术,有不同的数据,详见各自实现 void *apidata; /* This is used for polling API specific data */ // 新的循环前需要执行的操作 aeBeforeSleepProc *beforesleep;} aeEventLoop;
上面的数据结构能给我们很好的提示:事件循环结构体维护 I/O 事件表,定时事件表和触发事件表。
事件循环中心
redis 的主函数中调用 initServer() 函数从而初始化事件循环中心(EventLoop),它的主要工作是在 aeCreateEventLoop() 中完成的。
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; // 进入事件循环前需要执行的操作,此项会在 redis main() 函数中设置 eventLoop->beforesleep = NULL; // 在这里,aeApiCreate() 函数对于每个 IO 多路复用模型的实现都有不同,具体参见源代码,因为每种 IO 多路复用模型的初始化都不同 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 events[i].mask = AE_NONE; return eventLoop;err: if (eventLoop) { zfree(eventLoop->events); zfree(eventLoop->fired); zfree(eventLoop); } return NULL;}