一:文件事件
1:文件事件处理器
Redis基于Reactor模式开发了自己的网络时间处理器:这个处理器被称为 文件事件处理器。
1:文件事件处理器使用IO多路复用来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的 事件处理器
2:当被监听的套接字准备好执行 连接应答,读取,写入,关闭等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
文件事件处理器有四个组成部分,它们分别是套接字、I/O多路复用程序、文件事件分派器以及事件处理器
文件事件是对套接字操作的抽象,每当一个套接字准备好执行 accept、read、write和 close 等操作时,就会产生一个文件事件。因为 Redis 通常会连接多个套接字,所以多个文件事件有可能并发的出现。
I/O多路复用程序负责监听多个套接字,并向文件事件派发器传递那些产生了事件的套接字。
I/O多路复用程序总是会将所有产生的套接字都放到同一个队列(也就是后文中描述的aeEventLoop的fired就绪事件表)里边,然后文件事件处理器会以有序、同步、单个套接字的方式处理该队列中的套接字,也就是处理就绪的文件事件。
文件事件分派器 接收IO多路复用程序传来的套接字,并根据套接字 产生的事件的类型,调用相应的事件处理器。这些事件处理器时一种函数,定义了某个事件发生时,服务器应该执行的动作。
2:文件事件的类型
1:当套接字变得可读时(客户端对套接字执行write操作,或close操作),或有新的可应答套接字时,套接字产生 AE_READBLE事件
2:客户端对套接字执行read操作,套接字变得可写时,套接字产生 AE_WRITABLE 事件
如果一个套接字同时产生了这两种事件,文件事件分配器会优先处理 AE_READBLE事件,处理完后才处理 AE_WRITABLE事件
3:文件事件处理器的种类
连接应答处理器
networking.c/acceptTcpHandler 函数是 Redis 的连接应答处理器, 这个处理器用于对连接服务器监听套接字的客户端进行应答, 具体实现为 sys/socket.h/accept 函数的包装。 Redis 服务器进行初始化的时候, 程序会将这个连接应答处理器和服务器监听套接字的 AE_READABLE 事件关联起来, 当有客户端用 sys/socket.h/connect 函数连接服务器监听套接字的时候, 套接字就会产生 AE_READABLE 事件, 引发连接应答处理器执行, 并执行相应的套接字应答操作
命令请求处理器
networking.c/readQueryFromClient 函数是 Redis 的命令请求处理器, 这个处理器负责从套接字中读入客户端发送的命令请求内容, 具体实现为 unistd.h/read 函数的包装。
当一个客户端通过连接应答处理器成功连接到服务器之后, 服务器会将客户端套接字的 AE_READABLE 事件和命令请求处理器关联起来, 当客户端向服务器发送命令请求的时候, 套接字就会产生 AE_READABLE 事件, 引发命令请求处理器执行, 并执行相应的套接字读入操作。在客户端连接服务器的整个过程中, 服务器都会一直为客户端套接字的 AE_READABLE 事件关联命令请求处理器。
命令回复处理器
networking.c/sendReplyToClient 函数是 Redis 的命令回复处理器, 这个处理器负责将服务器执行命令后得到的命令回复通过套接字返回给客户端, 具体实现为 unistd.h/write 函数的包装。
当服务器有命令回复需要传送给客户端的时候, 服务器会将客户端套接字的 AE_WRITABLE 事件和命令回复处理器关联起来, 当客户端准备好接收服务器传回的命令回复时, 就会产生 AE_WRITABLE 事件, 引发命令回复处理器执行, 并执行相应的套接字写入操作。当命令回复发送完毕之后, 服务器就会解除命令回复处理器与客户端套接字的 AE_WRITABLE 事件之间的关联。
一次完整的客户端与服务器连接事件示例
让我们来追踪一次 Redis 客户端与服务器进行连接并发送命令的整个过程, 看看在过程中会产生什么事件, 而这些事件又是如何被处理的。
假设一个 Redis 服务器正在运作, 那么这个服务器的监听套接字的 AE_READABLE 事件应该正处于监听状态之下, 而该事件所对应的处理器为连接应答处理器。
如果这时有一个 Redis 客户端向服务器发起连接, 那么监听套接字将产生 AE_READABLE 事件, 触发连接应答处理器执行: 处理器会对客户端的连接请求进行应答, 然后创建客户端套接字, 以及客户端状态, 并将客户端套接字的 AE_READABLE 事件与命令请求处理器进行关联, 使得客户端可以向主服务器发送命令请求。
之后, 假设客户端向主服务器发送一个命令请求, 那么客户端套接字将产生 AE_READABLE 事件, 引发命令请求处理器执行, 处理器读取客户端的命令内容, 然后传给相关程序去执行。
执行命令将产生相应的命令回复, 为了将这些命令回复传送回客户端, 服务器会将客户端套接字的 AE_WRITABLE 事件与命令回复处理器进行关联: 当客户端尝试读取命令回复的时候, 客户端套接字将产生 AE_WRITABLE 事件, 触发命令回复处理器执行, 当命令回复处理器将命令回复全部写入到套接字之后, 服务器就会解除客户端套接字的 AE_WRITABLE 事件与命令回复处理器之间的关联。
二:时间事件
Redis的时间事件分为两类:
定时事件:让一段程序在指定时间之后执行一次。
周期性时间,让一段程序每隔指定时间就执行一次。
时间事件主要属性:
when:以毫秒格式的 UNIX 时间戳为单位,记录了时间事件到达时间。
timeProc :时间事件处理器,一个函数。当时间事件到达时,就会调用。
id:服务器为时间事件创建的全局唯一ID,从小到大顺序递增。新时间的ID号比旧事件的ID号要大。
next:指向下一个时间事件,形成无序链表结构,该无序体现在并非按照到达时间进行排序。新的时间事件是插入在该链表的表头位置
一个时间事件是定时事件还是周期性事件取决于时间处理器的返回值:
如果返回值是 AE_NOMORE,那么这个事件是一个定时事件,该事件在达到后删除,之后不会再重复。
如果返回值是非 AE_NOMORE 的值,那么这个事件为周期性事件,当一个时间事件到达后,服务器会根据时间处理器的返回值,对时间事件的 when 属性进行更新,让这个事件在一段时间后再次达到。
Redis 将所有时间事件都放在一个无序链表中,每次 Redis 会遍历整个链表,查找所有已经到达的时间事件,并且调用相应的事件处理器。
常用的API:
1、aeCreateTimeEvent:添加新的事件到链表表头
2、aeDeleteTimeEvent:删除给定id的时间事件
3、aeSearchNearestTimer:寻找离目前时间最近的时间事件
4、processTimeEvents:处理所有已到达的时间事件