Redis服务器是一个 事件驱动程序 ,所谓事件驱动就是输入一条命令并且按下回车,然后消息被组装成 Redis 协议的格式发送给 Redis 服务器,这个时候就会产生一个事件, Redis 服务器会接收改命令,处理该命令和发送回复,而当我们没有与服务器进行交互时,服务器就会处于阻塞等待状态,它会让出CPU然后进入睡眠状态,当事件触发时,就会被操作系统唤醒.
而Redis服务器需要处理以下两类事件:
文件事件
时间事件
Redis把 文件事件 和 时间事件 分别抽象成一个数据结构来管理.
typedef struct aeFileEvent {
// 文件时间类型:AE_NONE,AE_READABLE,AE_WRITABLE
int mask;
// 可读处理函数
aeFileProc *rfileProc;
// 可写处理函数
aeFileProc *wfileProc;
// 客户端传入的数据
void *clientData;
} aeFileEvent; //文件事件
其中 rfileProc 和 wfileProc 成员分别为两个函数指针,他们的原型为:
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
该函数是 回调函数 ,如果当前文件事件所指定的事件类型发生时,则会调用对应的 回调函数 来处理该事件.
当事件就绪的时候,我们需要知道文件事件的文件描述符还有事件类型才能对于锁定该事件,因此定义了 aeFiredEvent 结构统一管理:
typedef struct aeFiredEvent {
// 就绪事件的文件描述符
int fd;
// 就绪事件类型:AE_NONE,AE_READABLE,AE_WRITABLE
int mask;
} aeFiredEvent; //就绪事件
文件事件的类型:
#define AE_NONE 0 //未设置
#define AE_READABLE 1 //事件可读
#define AE_WRITABLE 2 //事件可写
typedef struct aeTimeEvent {
// 时间事件的id
long long id;
// 时间事件到达的时间的秒数
long when_sec; /* seconds */
// 时间事件到达的时间的毫秒数
long when_ms; /* milliseconds */
// 时间事件处理函数
aeTimeProc *timeProc;
// 时间事件终结函数
aeEventFinalizerProc *finalizerProc;
// 客户端传入的数据
void *clientData;
// 指向下一个时间事件
struct aeTimeEvent *next;
} aeTimeEvent; //时间事件
可以看出,时间事件的结构就是一个链表的节点,因为 struct aeTimeEvent *next 是指向下一个时间事件的指针.
和文件事件一样,当时间事件所指定的事件发生时,也会调用对应的 回调函数 ,结构成员 timeProc 和 finalizerProc 都是回调函数,函数原型如下:
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
虽然对文件事件和时间事件都做了抽象, Redis 仍然需要对事件做一个整体的抽象,用来描述一个事件的状态. 也就是下面要介绍的事件状态结构: aeEventLoop .
typedef struct aeEventLoop {
// 当前已注册的最大的文件描述符
int maxfd; /* highest file descriptor currently registered */
// 文件描述符监听集合的大小
int setsize; /* max number of file descriptors tracked */
// 下一个时间事件的ID
long long timeEventNextId;
// 最后一次执行事件的时间
time_t lastTime; /* Used to detect system clock skew */
// 注册的文件事件表
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; //事件轮询的状态结构
aeEventLoop 结构保存了一个 void * 类型的万能指针 apidata ,用来保存轮询事件的状态,也就是保存底层调用的多路复用库的事件状态.
Redis 的 I/O 多路复用程序的所有功能都是通过包装常见的 select 、 epoll 、 evport 和 kqueue 这些 I/O 多路复用函数库来实现的,每个I/O多路复用函数库在Redis源码中都对应着一个单独的文件,比如 ae_select.c 、 ae_epoll.c 等等.
他们在编译阶段,会根据不同的系统选择性能最高的一个多路复用库作为 Redis 的多路复用程序实现,而且所有库的API都是相同的,这就可以让Redis多路复用程序的底层可以互换.
下面是具体选择库的源码:
// IO复用的选择,性能依次下降,Linux支持 "ae_epoll.c" 和 "ae_select.c"
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
#ifdef HAVE_KQUEUE
#include "ae_kqueue.c"
#else
#include "ae_select.c"
#endif
#endif
#endif
也可以通过命令 INFO server 来查看当前使用的是哪个多路复用库: