1. Redis事件处理概述
Redis是一个基于内存的key-value存储系统,随着Redis集群的广泛应用,它的性能需要越来越高效,因此,Redis实现了异步的事件驱动模型,这个模型可以在减少Redis程序耗时间的同时,提高Redis的稳定性和运行效率。
2. Redis事件模型概述
Redis事件模型的核心是事件循环(Event Loop),由于Redis是单线程架构,所以事件循环每次只能处理一个事件(一个事件可能会有多个客户端在等待),即以队列的形式按照发生的先后顺序依次处理。事件循环会像操作系统一样,不断地将读写事件等待,直到事件发生。Redis事件模型是一种事件驱动、非阻塞的I/O模型,并且不需要创建额外的线程或进程,大大提高程序的效率。
3. Redis事件类型
Redis事件系统支持以下事件类型:
3.1 文件事件
文件事件是Redis对于客户端套接字操作的抽象,Redis使用Linux中的epoll来检测哪些套接字已经准备好读或写。
/* Create an event structure */
typedef struct aeEventLoop {
int maxfd; /* highest file descriptor currently registered */
int setsize; /* max number of file descriptors tracked */
int* wfd; /* File descriptors we want to write when they are ready */
int* rfd; /* File descriptors we want to read when data is available */
aeFileEvent* events; /* Registered events */
aeFiredEvent* fired; /* Fired events */
} aeEventLoop;
这个结构体代表了Redis事件处理器中的事件状态,其中rfd表示需要监听读事件的文件描述符集合,wfd表示需要监听写事件的文件描述符集合,events数组表示所有被监听的文件事件,fired表示所有已经被触发的文件事件。
3.2 时间事件
时间事件是Redis内部实现的一个定时器,Timerfd实现是对Linux系统提供的timerfd_create,timerfd_settime和timerfd_gettime的封装,其实现的核心是epoll机制,通过在events中注册自己,并依次按照剩余时间进行排序,存储在src.events数组中,然后每次执行事件循环中读取队列中剩余的定时器任务,检查是否命中超时时间。如果出现超时时间,会将时间事件分发到文件事件并处理。
/* Time event structure */
typedef struct aeTimeEvent {
long long id; /* time event identifier. */
long when_sec; /* seconds */
long when_ms; /* milliseconds */
aeTimeProc* timeProc; /* time event processing function */
aeEventFinalizerProc* finalizerProc; /* Finalizer function to cleanup the time event */
void* clientData; /* private client data to be passed to the processing function */
struct aeTimeEvent* next; /* point to the next timer event in the linked list */
} aeTimeEvent;
aeTimeEvent结构代表了Redis事件处理器中的定时事件状态,其中when_sec表示需要等待的秒数,when_ms表示需要等待的毫秒数,timeProc是处理定时事件的回调函数,finalizerProc表示执行完定时事件后的清理回调函数,clientData表示每个事件自定义的数据。
3.3 信号事件
Redis使用signalfd封装Linux系统提供的signal,将一个信号存在文件描述符中,然后在epoll中监听该事件。
/* Signal event structure */
typedef struct aeSignalEvent {
int sig; /* Signal number */
aeSignalHandler* sig_handler; /* Signal handler function */
void* clientData; /* Private data of the signal */
} aeSignalEvent;
aeSignalEvent表示了Redis事件处理器中的信号事件状态,其中sig表示信号的编号,sig_handler表示信号处理函数的地址,clientData表示每个事件自定义的数据。
4. Redis事件处理流程
Redis事件处理流程主要包括以下几个步骤:
4.1 初始化事件循环
Redis事件处理器的初始化函数会创建一个新的事件循环,并通过socket等系统调用实现信号,IO,异步Timer等事件的注册。初始化完成后,Redis事件循环会进入事件驱动模式,等待事件的触发和处理。
4.2 注册文件事件
Redis通过aeCreateFileEvent函数注册客户端套接字的读写事件,然后调用epoll_ctl,观察感兴趣的文件描述符是否有事件发生。每次执行时,Redis会依次遍历所有的已注册事件,然后将这些事件中处于可执行状态的放入队列中,并等待处理。
int aeCreateFileEvent(aeEventLoop* eventLoop, int fd, int mask, aeFileProc* proc, void* clientData) {
aeFileEvent* fe = NULL;
if ((mask & AE_READABLE && eventLoop->rfd[fd] <= 0) || (mask & AE_WRITABLE && eventLoop->wfd[fd] <= 0)) {
if (aeEpollAddEvent(eventLoop->epfd, fd, mask) == -1) {
return AE_ERR;
}
}
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
if ((fe = &eventLoop->events[fd]) == NULL) {
return AE_ERR;
}
if (mask & AE_READABLE) {
fe->mask |= AE_READABLE;
eventLoop->rfd[fd]++;
}
if (mask & AE_WRITABLE) {
fe->mask |= AE_WRITABLE;
eventLoop->wfd[fd]++;
}
fe->clientData = clientData;
fe->aeFileProc = proc;
fe-> fired = 0;
return AE_OK;
}
4.3 注册时间事件
Redis通过aeCreateTimeEvent函数创建定时器,并将其插入到对应的时间事件队列中,当到达设置的超时时间后,Redis会将定时事件分发到待处理文件事件的队列中,从而实现任务的定时执行。
long long aeCreateTimeEvent(aeEventLoop* eventLoop, long long milliseconds, aeTimeProc* proc, void* clientData, aeEventFinalizerProc* finalizerProc) {
long long id = eventLoop->timeEventNextId++;
aeTimeEvent* te;
te = zmalloc(sizeof(*te));
if (te == NULL) {
return AE_ERR;
}
te->id = id;
te->when_sec = milliseconds / 1000;
te->when_ms = milliseconds % 1000;
te->timeProc = proc;
te->clientData = clientData;
te->finalizerProc = finalizerProc;
te->next = eventLoop->timeEventHead;
eventLoop->timeEventHead = te;
return id;
}
4.4 注册信号事件
Redis通过aeCreateSignalEvent函数注册信号事件,并将其插入到对应的信号事件队列中,以便在接收到信号时快速将信号事件分发到待处理事件的队列中。
int aeCreateSignalEvent(aeEventLoop* eventLoop, int sig, aeSignalHandler* handler, void* clientData) {
aeSignalEvent event;
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
event.sig = sig;
event.sig_handler = handler;
event.clientData = clientData;
if (sigaction(sig, &sa, NULL) == -1) {
return AE_ERR;
}
if (aeEpollAddEvent(eventLoop->epfd, event.sig, AE_READABLE) != -1) {
eventLoop->events[event.sig] = event;
return AE_OK;
}
return AE_ERR;
}
4.5 执行事件循环
在事件循环中,Redis会不断地查询已经触发的事件,然后将触发的事件添加到事件队列中,并通过事件处理器中的函数处理四种不同类型的事件。最终,Redis会处理完队列中所有的事件并等待下一个事件的触发。
void aeMain(aeEventLoop* eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
4.6 处理事件
事件处理器通过aeProcessEvents函数来对Redis中的各种事件进行处理。aeProcessEvents函数首先判断用户是否选择了阻塞或非阻塞模式,然后使用目前的poll方法查询已经触发的事件,并将这些事件放在事件队列中。此时,Redis会调用已注册的处理程序来执行这些事件,从而实现事件的触发、处理和回调。
void aeProcessEvents(aeEventLoop* eventLoop, int flags) {
int processed = 0;
int numevents;
numevents = aeEpollWait(eventLoop, flags);
for (int i = 0; i < numevents; ++i) {
aeFileEvent* fe = &eventLoop->events[eventLoop->fired[i].fd];
int mask = eventLoop->fired[i].mask;
int fd = eventLoop->fired[i].fd;
int rfired = 0;
if (fe->mask & mask & AE_READABLE) {
rfired = 1;
fe->aeFileProc(eventLoop, fd, fe->clientData, mask);
processRegisterLuaScript(eventLoop,fd,EVENT_READ);
processed++;
}
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->aeFileProc != sendReplyToClient) {
fe->aeFileProc(eventLoop, fd, fe->clientData, mask);
processRegisterLuaScript(eventLoop,fd,EVENT_WRITE);
processed++;
}
}
}
}
5. 总结
Redis的事件驱动模型及其事件处理系统是Redis高性能、高稳定的重要组成部分。在每个事件中,Redis都在事件队列中记录事件的状态,并调用处理程序执行这些事件。这样,Redis就能够快速响应IO完成和定时器事件,并从中分离出长时间阻塞,提高了吞吐量和整体性能。