TimeEvent。
1,文件事件
以多路IO复用程序来同事监听多个套接字,处理网络连接的应答,读取,写入和关闭操作,并根据执行任务的不同分配不同的事件处理器来处理。
a, 结构体
/* File event structure */ /* 文件事件结构体 */ typedef struct aeFileEvent { //只为读事件或者写事件中的1种 int mask; /* one of AE_(READABLE|WRITABLE) */ //读事件方法, 根据不同任务关联不同的处理器,如应答、请求.. aeFileProc *rfileProc; //写事件方法,根据不同任务关联不同的处理器,如写入.. aeFileProc *wfileProc; //客户端数据,指向 redisClient 的指针 void *clientData; } aeFileEvent;
b, 处理文件事件的流程图
IO多路复用函数监听多个套接字,当套接字上注册的套接字注册的事件类型触发时,会将其放入队列中,顺序进行处理。处理时根据套接字上的事件类型调用关联的事件处理器。这里注意,如果一个套接字即可读又可写,则先处理读再处理写。
c, 文件事件的几种事件处理器
连接应答处理器,是与监听服务套接字的读事件进行关联的,当有新的客户端发起连接时(connect),连接应答处理器会accept并返回客户端套接字,创建客户端状态,添加到redis client链表,该结构维护了客户端的各种信息(具体之后讲)。并将读事件与客户端套接字进行关联。
命令请求处理器,是与客户端套接字的读事件进行关联,读取客户端发送来的命令并放入clientdata中的输入缓冲区,在进行协议解析并调用对应的命令处理..。.
命令回复处理器,是与客户端套接字的写事件进行关联,当命令请求后需要向客户端发送数据时,注册该事件,当客户端返回可以写入时,则事件触发,将数据写入。这里需要注意,因为redis是单线程的,因此当数据量超过一个阀值时,会重新注册并返回,避免阻塞过长时间,下次触发再发送。
2,时间事件
记录在指定时间点运行的事件,多个时间事件以无序链表的形式保存在服务器状态中。
a, 结构体
/* Time event structure */ /* 时间事件结构体 */ typedef struct aeTimeEvent { //时间事件id,累加增加,id降序链表 long long id; /* time event identifier. */ //时间秒数 long when_sec; /* seconds */ //时间毫秒 long when_ms; /* milliseconds */ //时间事件中的处理函数 aeTimeProc *timeProc; //被删除的时候将会调用的方法 aeEventFinalizerProc *finalizerProc; //客户端数据 void *clientData; //时间结构体内的下一个结构体 struct aeTimeEvent *next; } aeTimeEvent;
b, 定时事件和周期性事件
执行一次和重复执行的区别,定时需要在处理完后删除事件。周期需要更新when时间。根据事件处理器的返回值区分是那个时间事件。
c, 使用无序链表实现,每次需要遍历所有链表,效率低。但现在redis正常只有一个serverCron...benchmark下有两个。实现没含量,最小堆实现都比这个好,事件多了会死循环,切效率低,这里不多讲了。
3,事件的循环调度
a, 结构体
typedef struct aeFiredEvent { //文件描述符 fd 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;
结构体之间的关系如下图
b, 事件循环
基本就是用一个无限循环,然后再循环中去检测各个事件的发生。若服务器未关闭则一直循环,且先处理文件事件,再处理时间事件。首先计算定时时间的最快触发时间,这样保证了阻塞时间不会影响时间事件的处理,同时避免频繁地轮询时间事件。然后是阻塞时间等待文件IO事件的触发,如果等待时间内内有文件事件,阻塞时间超时返回,时间事件正好触发。若阻塞时间内触发文件IO事件,则直接返回处理,这是时间事件还未触发,则进入新的循环,重新计算时间事件的时间,这样再处理文件事件的同时,逐步逼近时间事件。流程图如下:
注意时间事件不是准时的,只能保证在定时的时间之后处理而不是按时处理。