redis事件处理流程分析

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完成和定时器事件,并从中分离出长时间阻塞,提高了吞吐量和整体性能。

数据库标签