Redis 事件机制详解

redis 采用事件驱动机制来处理大量的网络IO 。它并没有使用 libevent 或者 libev 这样的成熟开源方案 , 而是自己实现一个非常简洁的事件驱动库 ae_event 。

Redis 事件机制详解

文章插图
 
Redis中的事件驱动库只关注网络IO , 以及定时器 。该事件库处理下面两类事件:
  • 文件事件(file event):用于处理 Redis 服务器和客户端之间的网络IO 。
  • 时间事件(time eveat):Redis 服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行 , 而时间事件就是处理这类定时操作的 。
事件驱动库的代码主要是在src/ae.c中实现的 , 其示意图如下所示 。
Redis 事件机制详解

文章插图
 
aeEventLoop 是整个事件驱动的核心 , 它管理着文件事件表和时间事件列表 , 不断地循环处理着就绪的文件事件和到期的时间事件 。下面我们就先分别介绍文件事件和时间事件 , 然后讲述相关的 aeEventLoop 源码实现 。
文件事件
Redis基于Reactor模式开发了自己的网络事件处理器 , 也就是文件事件处理器 。文件事件处理器使用IO多路复用技术 , 同时监听多个套接字 , 并为套接字关联不同的事件处理函数 。当套接字的可读或者可写事件触发时 , 就会调用相应的事件处理函数 。
Redis 使用的IO多路复用技术主要有: select 、 epoll 、 evport 和 kqueue等 。每个IO多路复用函数库在 Redis 源码中都对应一个单独的文件 , 比如ae select.c , ae epoll.c ,  ae_kqueue.c等 。Redis 会根据不同的操作系统 , 按照不同的优先级选择多路复用技术 。事件响应框架一般都采用该架构 , 比如 netty 和 libevent 。
Redis 事件机制详解

文章插图
 
如下图所示 , 文件事件处理器有四个组成部分 , 它们分别是套接字、I/O多路复用程序、文件事件分派器以及事件处理器 。
Redis 事件机制详解

文章插图
 
文件事件是对套接字操作的抽象 , 每当一个套接字准备好执行 accept、read、write和 close 等操作时 , 就会产生一个文件事件 。因为 Redis 通常会连接多个套接字 , 所以多个文件事件有可能并发的出现 。
I/O多路复用程序负责监听多个套接字 , 并向文件事件派发器传递那些产生了事件的套接字 。
尽管多个文件事件可能会并发地出现 , 但I/O多路复用程序总是会将所有产生的套接字都放到同一个队列(也就是后文中描述的 aeEventLoop 的 fired 就绪事件表)里边 , 然后文件事件处理器会以有序、同步、单个套接字的方式处理该队列中的套接字 , 也就是处理就绪的文件事件 。
Redis 事件机制详解

文章插图
 
所以 , 一次 Redis 客户端与服务器进行连接并且发送命令的过程如上图所示 。
  • 客户端向服务端发起建立 socket 连接的请求 , 那么监听套接字将产生 AE READABLE 事件 , 触发 连接应答处理器 执行 。处理器会对客户端的连接请求进行应答 , 然后创建客户端套接字 , 以及客户端状态 , 并将客户端套接字的 AE READABLE 事件与 命令请求处理器 关联 。
  • 客户端建立连接后 , 向服务器发送命令 , 那么客户端套接字将产生 AE_READABLE 事件 , 触发 命令请求处理器 执行 , 处理器读取客户端命令 , 然后传递给相关程序去执行 。
  • 执行命令获得相应的命令回复 , 为了将命令回复传递给客户端 , 服务器将客户端套接字的 AEWRITEABLE 事件与 命令回复处理器 关联 。当客户端试图读取命令回复时 , 客户端套接字产生 AE WRITEABLE 事件 , 触发命令 回复处理器 将命令回复全部写入到套接字中 。
时间事件
Redis 的时间事件分为以下两类:
  • 定时事件:让一段程序在指定的时间之后执行一次 。
  • 周期性事件:让一段程序每隔指定时间就执行一次 。
Redis 的时间事件的具体定义结构如下所示 。
一个时间事件是定时事件还是周期性事件取决于时间处理器的返回值:


推荐阅读