Redis 事件机制详解( 二 )


  • 如果返回值是 AE_NOMORE , 那么这个事件是一个定时事件 , 该事件在达到后删除 , 之后不会再重复 。
  • 如果返回值是非 AE_NOMORE 的值 , 那么这个事件为周期性事件 , 当一个时间事件到达后 , 服务器会根据时间处理器的返回值 , 对时间事件的 when 属性进行更新 , 让这个事件在一段时间后再次达到 。
Redis 将所有时间事件都放在一个无序链表中 , 每次 Redis 会遍历整个链表 , 查找所有已经到达的时间事件 , 并且调用相应的事件处理器 。
介绍完文件事件和时间事件 , 我们接下来看一下 aeEventLoop 的具体实现 。
创建事件管理器
Redis 服务端在其初始化函数 initServer 中 , 会创建事件管理器 aeEventLoop 对象 。
函数 aeCreateEventLoop 将创建一个事件管理器 , 主要是初始化 aeEventLoop 的各个属性值 , 比如 events 、 fired 、 timeEventHead 和 apidata :
  • 首先创建 aeEventLoop 对象 。
  • 初始化未就绪文件事件表、就绪文件事件表 。events 指针指向未就绪文件事件表、 fired指针指向就绪文件事件表 。表的内容在后面添加具体事件时进行初变更 。
  • 初始化时间事件列表 , 设置 timeEventHead 和 timeEventNextId 属性 。
  • 调用 aeApiCreate 函数创建 epoll 实例 , 并初始化 apidata。
aeApiCreate 函数首先创建了 aeApiState 对象 , 初始化了epoll就绪事件表;然后调用 epoll_create 创建了 epoll 实例 , 最后将该 aeApiState 赋值给 apidata 属性 。
aeApiState 对象中 epfd 存储 epoll 的标识 ,  events 是一个 epoll 就绪事件数组 , 当有 epoll 事件发生时 , 所有发生的 epoll 事件和其描述符将存储在这个数组中 。这个就绪事件数组由应用层开辟空间、内核负责把所有发生的事件填充到该数组 。
创建文件事件
aeFileEvent 是文件事件结构 , 对于每一个具体的事件 , 都有读处理函数和写处理函数等 。Redis 调用 aeCreateFileEvent 函数针对不同的套接字的读写事件注册对应的文件事件 。
比如说 , Redis 进行主从复制时 , 从服务器需要主服务器建立连接 , 它会发起一个 socekt连接 , 然后调用 aeCreateFileEvent 函数针对发起的socket的读写事件注册了对应的事件处理器 , 也就是 syncWithMaster 函数 。
aeCreateFileEvent 的参数 fd 指的是具体的 socket 套接字 ,  proc 指 fd 产生事件时 , 具体的处理函数 ,  clientData 则是回调处理函数时需要传入的数据 。aeCreateFileEvent 主要做了三件事情:
  • 以 fd 为索引 , 在 events 未就绪事件表中找到对应事件 。
  • 调用 aeApiAddEvent 函数 , 该事件注册到具体的底层 I/O 多路复用中 , 本例为epoll 。
  • 填充事件的回调、参数、事件类型等参数 。
如上文所说 , Redis 基于的底层 I/O 多路复用库有多套 , 所以 aeApiAddEvent 也有多套实现 , 下面的源码是 epoll 下的实现 。其核心操作就是调用 epoll 的 epoll_ctl 函数来向 epoll 注册响应事件 。有关 epoll 相关的知识可以看一下《JAVA NIO源码分析》
事件处理
因为 Redis 中同时存在文件事件和时间事件两个事件类型 , 所以服务器必须对这两个事件进行调度 , 决定何时处理文件事件 , 何时处理时间事件 , 以及如何调度它们 。
aeMain 函数以一个无限循环不断地调用 aeProcessEvents 函数来处理所有的事件 。
下面是 aeProcessEvents 的伪代码 , 它会首先计算距离当前时间最近的时间事件 , 以此计算一个超时时间;然后调用 aeApiPoll 函数去等待底层的I/O多路复用事件就绪; aeApiPoll函数返回之后 , 会处理所有已经产生文件事件和已经达到的时间事件 。
与 aeApiAddEvent 类似 ,  aeApiPoll 也有多套实现 , 它其实就做了两件事情 , 调用 epoll_wait 阻塞等待 epoll 的事件就绪 , 超时时间就是之前根据最快达到时间事件计算而来的超时时间;然后将就绪的 epoll 事件转换到fired就绪事件 。aeApiPoll 就是上文所说的I/O多路复用程序 。具体过程如下图所示 。
Redis 事件机制详解

文章插图
 
processFileEvent 是处理就绪文件事件的伪代码 , 也是上文所述的文件事件分派器 , 它其实就是遍历 fired 就绪事件表 , 然后根据对应的事件类型来调用事件中注册的不同处理器 , 读事件调用 rfileProc  , 而写事件调用 wfileProc。


推荐阅读