文章目录

  • 事件驱动机制
  • 文件事件
  • 时间事件
  • aeEventLoop(事件管理器)的具体实现
  • 创建文件事件
  • 事件处理
  • 删除事件


事件驱动机制

Redis采用事件驱动机制来处理大量的网络IO,Redis中的事件驱动库只关注网络IO以及定时器。

事件库处理两类事件:

  • 文件事件:用于处理Redis服务器和客户端之间的网络IO.
  • 事件事件:Redis服务器中的一些操作需要在给定的时间点执行,而时间事件就是处理这类定时操作的。
    事件驱动库的代码主要是在src/ae.c中实现的,示意图如下:

文件事件

Redis基于Reactor模式开发了自己的网络事件处理器,也就是文件事件处理器。该处理器采用多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理函数。当套接字的可读或者可写事件触发时,就会调用相应的事件处理函数。

1. 为什么单线程的Redis能那么快?

Redis的瓶颈主要在IO而不是CPU,所以为了省开发量,在6.0版本前是单线程模型;其次,Redis的单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程,但Redis的其他功能,比如持久化、异步删除、集群数据同步等,是由额外的线程执行的。

Redis采用了多路复用机制使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率

2. Redis事件响应框架ae_event及文件事件处理器

Redis自己实现了一个非常简洁的事件驱动库ae_event.

Redis使用的IO多路复用技术主要有:select、epoll、evport、kqueue等。每个IO多路复用函数库在Redis源码中都对应一个单独的文件。Redis会根据不同的操作系统,按照不同的优先级选择多路复用技术。事件响应框架一般都采用该框架,比如netty和libevent。

redis 时间作为key值会重复吗 redis时间事件_套接字


文件事件处理器由四部分组成:套接字、I/O多路复用程序、文件事件分派器、事件处理器

redis 时间作为key值会重复吗 redis时间事件_套接字_02


文件事件是对套接字操作的抽象,每当一个套接字准备好执行accept、read、write、close等操作时,就会产生一个文件事件,因为Redis通常会连接多个套接字,所以多个文件事件有可能并发的出现。

I/O多路多路复用程序负责监听多个套接字,并向文件事件派发器传递那些产生了事件的套接字。

尽管多个文件事件可能会并发的出现,但I/O多路复用程序总是会将所有产生的套接字都放到同一个队列里边,然后文件事件处理器会以有序、同步、单个套接字的方式处理该队列中的套接字,也就是处理就绪的文件事件。

redis 时间作为key值会重复吗 redis时间事件_redis_03


一次Redis客户端与服务器进行连接并且发送命令的过程如下:

  1. 客户端向服务器发起建立socket连接的请求,监听套接字将产生AE_READABLE事件,触发连接应答处理器执行。
  2. 连接应答处理器会对客户端进行应答,然后创建客户端套接字以及客户端状态,并将客户端套接字的AE_READABLE事件与命令请求处理器关联。
  3. 客户端建立连接后,向服务器发送命令,那么客户端套接字将产生AE_READABLE事件,触发命令请求处理器执行,处理器读取客户端命令,然后传递给相关程序去执行。
  4. 执行命令获得相应的命令回复,为了将命令回复传递给客户端,服务器将客户端套接字的AE_WRITEABLE事件与命令回复处理器关联。当客户端试图读取命令回复时,客户端套接字产生AE_WRITEABLE事件,触发命令回复处理器将命令回复全部写入到套接字中。

3. 多路复用模型

在Redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给Redis线程处理,这就实现了一个Redis线程处理多个IO流的效果。

多路复用的Redis IO模型:

redis 时间作为key值会重复吗 redis时间事件_redis_04


基于多路复用的Redis高性能IO模型为了在请求到达时能够通知到Redis线程,select/epoll提供了基于事件的回调机制,针对不同事件的发生,调用相应的处理函数。

回调机制:

select/epoll一旦检测到FD上有请求到达时,就会触发相应的时间。这些事件会被放到一个事件队列,Redis单线程对该事件队列不断进行处理,这样一来,Redis无需一直轮询是否有请求实际发生,这就可以避免造成CPU资源浪费。同时Redis在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。因为Redis一直在对事件队列进行处理,所以能及时响应客户端的请求,提升Redis的响应性能

时间事件

Redis中的时间事件分为两类:

  • 定时事件:让一段程序在指定的时间之后执行一次。
  • 周期性时间:让一段程序每隔指定时间就执行一次。

一个时间事件是定时事件还是周期性事件取决于处理器的返回值:

  • 如果返回值是AE_NOMORE,那么这个事件是一个定时事件,该事件在达到后就会删除,之后不再重复。

  • 如果返回值是AE_NOMORE的值,那么就是一个周期性事件,当一个时间事件到达后,服务器就会根据时间处理器的返回值,对时间事件的when属性进行更新,让这个事件在一定时间后再次达到。
  • redis 时间作为key值会重复吗 redis时间事件_套接字_05

服务器的所有的时间事件都放在一个无序链表中,每当时间事件执行器运行时,它就遍历整个链表,查找所有已到达的时间事件,并调用相应的事件处理器。正常模式下的Redis服务器只使用serverCron一个时间事件,而在benchmark模式下,服务器也只使用两个时间事件,所以不影响事件执行的性能。

aeEventLoop(事件管理器)的具体实现

Redis服务器端在其初始化函数initServer中,会创建事件管理器aeEventLoop对象。
函数aeCreateEventLoop将创建一个事件管理器,主要是初始化aeEventLoop的各个属性值,比如events、fired、timeEventHead和apidata

  • 首先创建aeEventLoop对象。
  • 初始化未就绪文件事件表、就绪文件事件表。events指向未就绪文件事件表、fired指向就绪文件事件表,表的内容在后面添加具体事件时进行初变更。
  • 初始化时间事件列表,设置timeEventHead和timeEventNextId属性
  • 调用aeApiCreate函数创建epoll实例,并初始化apidata
    在这里需要补充一下关于epoll的知识:

epoll是linux内核实现IO多路复用的一个实现,IO多路复用的意思是在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其进行读写操作。epoll监听的fd(套接字)集合是常驻内核的,他有三个系统调用(epoll_create、epoll_wait、epoll_ctl),通过epoll_wait可以多次监听同一个fd集合,只返回可读写那部分。

aeApiCreate函数的说明:

该函数首先创建了aeApiState对象,初始化了epoll就绪事件表;然后调用epoll_create创建了epoll实例,最后将该aeApiState赋值给apidata属性(事件管理器与epoll关联)。

aeApiState对象中的epfd存储了epoll的标识,events是一个epoll就绪事件数组,当有epoll事件发生的时候,所有发生的epoll事件和其描述符存储在这个数组中,这个就绪事件数组由应用层开辟空间、内核负责把所有发生的事件填充到该数组。

创建文件事件

aeFileEvent是文件事件结构,对于每一个具体的事件,都有读处理函数和协处理函数。Redis调用aeCreateFileEvent函数针对不同的套接字的读写事件注册对应的文件事件
aeCreateFileEvent函数主要做了三件事:

  • 以fd为索引,在events未就绪事件表中找到对应的事件。
  • 调用aeApiAddEvent函数,该事件注册到具体的底层IO多路复用中,本例为epoll(核心操作是调用epoll的epoll_ctl函数来向epoll注册响应事件
  • 填充事件的回调、参数、事件类型等参数。

事件处理

因为Redis中同时存在文件事件和时间事件两个事件类型,所以服务器必须对这两个事件进行调度,决定何时处理文件事件,何时处理时间事件,以及如何调度他们。

aeMain函数以一个无限循环不断的调用aeProcessEvents函数来处理所有的事件。aeProcessEvents的处理过程为:

他会首先计算距离当前时间最近的时间事件,以此计算一个超时时间;然后调用aeApiPoll函数去等待底层的IO多路复用事件就绪;aeApiPoll函数返回之后,会处理所有已经产生文件事件和已经达到的时间事件。

aeApiPoll就做两件事:调用epoll_wait阻塞等待epoll的事件就绪,超时时间就是之前根据最快达到时间事件计算而来的超时时间;然后将就绪的epoll事件转换到fired就绪事件。aeApiPoll就是上文所说的IO多路复用程序,具体过程如下图所示:

redis 时间作为key值会重复吗 redis时间事件_redis 时间作为key值会重复吗_06


processFileEvent就是处理就绪文件事件的函数,也就是文件事件分派器,他其实就是遍历fired就绪事件表,然后根据对应的事件类型来调用事件中注册的不同处理器,读事件调用rfileProc,而写事件调用wfileProc

processTimeEvents是处理时间事件的函数,他会遍历aeEventLoop的时间事件列表,如果时间事件到达就执行其timeProc函数,并根据函数的返回值是否等于AE_NOMORE来决定该时间事件是否是周期性事件,并修改到达时间。

删除事件

当不再需要某个事件时,需要把事件删除掉。调用aeDeleteEventLoop函数,该函数的执行过程为:

  • 根据fd在未就绪表中查找到事件
  • 取消该fd对应的相应事件标识符
  • 调用aeApiFree函数,内核会将epoll监听红黑树上的相应事件监听取消