文章基于redis-4.0.1源码详细介绍一下redis的事件模型。
一、redis事件模型概览
redis是一个事件驱动的服务程序,在redis的服务程序中存在两种类型的事件,分别是文件事件和时间事件。文件事件是对网络通信操作的统称,时间事件是redis中定时运行的任务或者是周期性的任务(目前redis中只有serverCron这一个周期性时间事件,并没有定时时间事件)。对于事件驱动类的程序,非常适合使用Reactor模式进行设计(如果要详细了解Reactor模式,请参考超链接中的博客)。redis也不例外,在文件事件处理的设计中采用了Reactor设计模式。
下面对应于链接博客中Reactor模式图仔细讲解一下redis如何使用Reactor模式实现高效的文件事件模型。为了方便,首先将Reactor设计模式图作为图1放在本文中。
图 1 Reactor设计模式图
Reactor模式包含四部分,分别是Handle(对于系统资源的一种抽象,在redis中就是监听描述符或者是连接描述符)、Synchronous Event Demultiplexer(同步事件分离器,在redis中对应于IO多路复用程序)、Event Handler(事件处理器,在redis中对应于连接应答处理器、命令请求处理器以及命令回复处理器、事件处理器等)和Initiation Dispatcher(事件分派器,在redis中对应于ae.c/aeProcessEvents函数)。
在redis中将感兴趣的事件及类型(读、写)通过IO多路复用程序注册到内核中并监听每个事件是否发生。当IO多路复用程序返回的时候,如果有事件发生,redis在封装IO多路复用程序时,将所有已经发生的事件及该事件的类型封装为aeFiredEvent类型,放到aeEventLoop的fired成员中,形成一个队列。通过这个队列,redis以有序、同步、每次一个套接字事件的方式向文件事件分派器传送套接字,并处理发生的文件事件。redis处理事件(无论是文件事件还是时间事件)都是以原子的方式进行的,中间不存在事件之间的抢占。这很容易理解,redis是单线程模型,不存在处理上的并发操作。
最后需要说明的是redis首先处理发生的文件事件,然后才会处理时间事件,这点我们在介绍redis源码aeProcessEvents的时候会详细注释和介绍。
二、redis实现事件模型使用的数据结构
redis表示事件模型的数据结构是对该事件标识、事件类型和事件处理函数的一种抽象,就是Reactor模式中的Handle和Event Handle的集合。redis使用了四种数据结构描述redis中的事件,前三种数据结构是对redis中某种特定类型事件的一种抽象,最后一种数据结构aeEventLoop是redis管理所有事件的一种抽象。aeTimeEvent中的id成员、aeFiredEvent中的fd成员都是Reactor模式中所说的Handle的具体表现,但是好像aeFileEvents并没有对应的handle。其实,redis在aeEventLoop的events成员中使用每一个描述符fd作为下标,该下标的对应值为aeFileEvent成员,由此将描述符fd与对该fd感兴趣的事件类型以及处理函数相关联,对应于Reactor中Handle与Event Handler的关联。当通过aeEventLoop中的fired获取到已经发生的事件fd及其类型mask的时候,由fd和mask在aeEventLoop的events成员中获取对应的事件处理器,处理已经发生的事件。也就是说,文件事件的处理是联合使用了fired和events两个成员变量;时间事件的处理使用aeTimeEvent变量。
文件事件数据结构。
/* 文件事件 */
typedef struct aeFileEvent {
/* 套接字发生的事件,读事件或者写事件其中的一种 */
int mask; /* one of AE_(READABLE|WRITABLE) */
/* 读事件处理器,回调函数 */
aeFileProc *rfileProc;
/* 写事件处理器,回调函数 */
aeFileProc *wfileProc;
/* 客户端数据 */
void *clientData;
} aeFileEvent;
时间事件数据结构。
typedef struct aeTimeEvent {
/* 时间事件,每个时间事件通过id唯一标识 */
long long id;
/* 时间事件应该触发的时间,单位:s */
long when_sec;
/* 时间事件被触发的时间,单位:ms */
long when_ms;
/* 时间事件处理函数 */
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
/* 客户端数据 */
void *clientData;
/* 时间事件形成的链条 */
struct aeTimeEvent *next;
} aeTimeEvent;
已经发生的文件事件数据结构。
/* 已经发生的文件事件 */
typedef struct aeFiredEvent {
int fd;
int mask;
} aeFiredEvent;
redis中时间管理结构体,包含了文件事件、时间事件、已发生的文件事件等相关信息。
/* redis中的事件管理结构体 */
typedef struct aeEventLoop {
/* 当前IO程序追踪的最大的文件描述符,大于此值的setsize范围内的值,没有意义