1 Redis事件介绍
Redis 采用事件驱动机制来处理大量的网络IO。它并没有使用 libevent 或者 libev 这样的成熟开源方案,而是自己实现一个非常简洁的事件驱动库 ae_event。
Redis中的事件驱动库只关注网络IO,以及定时器。该事件库处理下面两类事件:
-
文件事件(file event):用于处理 Redis 服务器和客户端之间的网络IO。
-
时间事件(time event):Redis 服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是处理这类定时操作的。
事件驱动库的代码主要是在src/ae.c中实现的,其示意图如下所示。

aeEventLoop
是整个事件驱动的核心,它管理着文件事件表和时间事件列表,不断地循环处理着就绪的文件事件和到期的时间事件。
2 文件事件
Redis基于Reactor模式开发了自己的网络事件处理器,也就是文件事件处理器。文件事件处理器使用IO多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理函数。当套接字的可读或者可写事件触发时,就会调用相应的事件处理函数。
Redis 使用的IO多路复用技术主要有:select
、 epoll
、 evport
和 kqueue
等。Redis 会根据不同的操作系统,按照不同的优先级选择多路复用技术。事件响应框架一般都采用该架构,比如 netty 和 libevent。

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

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

所以,一次 Redis 客户端与服务器进行连接并且发送命令的过程如上图所示。
-
客户端向服务端发起建立 socket 连接的请求,那么监听套接字将产生 AE_READABLE 事件,触发连接应答处理器执行。处理器会对客户端的连接请求进行应答,然后创建客户端套接字,以及客户端状态,并将客户端套接字的 AE_READABLE 事件与命令请求处理器关联。
-
客户端建立连接后,向服务器发送命令,那么客户端套接字将产生 AE_READABLE 事件,触发命令请求处理器执行,处理器读取客户端命令,然后传递给相关程序去执行。
-
执行命令获得相应的命令回复,为了将命令回复传递给客户端,服务器将客户端套接字的 AE_WRITEABLE 事件与命令回复处理器关联。当客户端试图读取命令回复时,客户端套接字产生 AE_WRITEABLE 事件,触发命令回复处理器将命令回复全部写入到套接字中。
3 时间事件
Redis 的时间事件分为以下两类:
-
定时事件:让一段程序在指定的时间之后执行一次。
-
周期性事件:让一段程序每隔指定时间就执行一次。
一个时间事件是定时事件还是周期性事件取决于时间处理器的返回值:
-
如果返回值是 AE_NOMORE,那么这个事件是一个定时事件,该事件在达到后删除,之后不会再重复。
-
如果返回值是非 AE_NOMORE 的值,那么这个事件为周期性事件,当一个时间事件到达后,服务器会根据时间处理器的返回值,对时间事件的
when
属性进行更新,让这个事件在一段时间后再次达到。
Redis 将所有时间事件都放在一个无序链表中,每次 Redis 会遍历整个链表,查找所有已经到达的时间事件,并且调用相应的事件处理器。
4 serverCron函数
Redis服务器中的serverCron函数默认每隔100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运转,在serverCron函数中会对以下属性进行更新:
-
缓存的秒级精度系统时间和毫秒级精度系统时间(默认100毫秒更新一次)
-
缓存的lrulock属性,保存了服务器的LRU时钟,主要用于给对象计算键空转时间(空转时间=对象LRU时间-服务器的lrulock属性)。(默认10s更新一次)
-
更新服务器每秒执行命令次数
instantaneous_ops_per_sec
,是通过计算服务器每1毫秒内执行命令数*1000估算出来的。 -
更新服务器内存峰值记录。
-
在启动服务器时,Redis会为服务器进程的SIGTERM信号关联处理器sigtermHandler函数,这个信号处理器负责在服务器接到SIGTERM信号时,打开服务器状态的
shutdown_asap
标识,“每次serverCron函数运行时,程序都会对服务器状态的shutdown_asap
属性进行检查,并根据属性的值决定是否关闭服务器。(关闭之前会进行RDB持久化) -
管理客户端资源,serverCron函数每次执行都会调用clientsCron函数,clientsCron函数会对一定数量的客户端进行以下两个检查:
-
如果客户端与服务器之间的连接已经超时(很长一段时间里客户端和服务器都没有互动),那么程序释放这个客户端。
-
如果客户端在上一次执行命令请求之后,输入缓冲区的大小超过了一定的长度,那么程序会释放客户端当前的输入缓冲区,并重新创建一个默认大小的输入缓冲区,从而防止客户端的输入缓冲区耗费了过多的内存。
-
-
管理数据库资源。对数据库进行检查,删除过期键。
-
在服务器执行BGSAVE命令的期间,如果客户端向服务器发来BGREWRITEAOF命令,那么服务器会将BGREWRITEAOF命令的执行时间延迟到BGSAVE命令执行完毕之后,serverCron函数会检查是否有被延迟执行的BGREWRITEAOF命令,如果有,并且当前没有BGSAVE和BGREWRITEAOF命令在执行,那么就会执行BGREWRITEAOF命令。
-
持久化操作检查。serverCron函数会检查
rdb_child_pid
和aof_child_pid
两个属性来判断当前是否在在进行持久化操作,在的话,执行wait3函数,检查子进程是否有信号发来服务器进程:-
如果有信号到达,那么表示新的RDB文件已经生成完毕(对于BGSAVE命令来说),或者AOF文件已经重写完毕(对于BGREWRITEAOF命令来说),服务器需要进行相应命令的后续操作,比如用新的RDB文件替换现有的RDB文件,或者用重写后的AOF文件替换现有的AOF文件。
-
如果没有信号到达,那么表示持久化操作未完成,程序不做动作。如果没有在进行持久化,那么会判断当前是否满足进行RDB持久话或者AOF持久化的条件,满足就执行相关操作。如下图所示:
-
-
如果服务器开启了AOF持久化功能,并且AOF缓冲区里面还有待写入的数据,那么serverCron函数会调用相应的程序,将AOF缓冲区中的内容写入到AOF文件里面。
-
关闭异步客户端,服务器会关闭那些输出缓冲区大小超出限制的客户端。
-
增加cronloops计数器的值。服务器状态的cronloops属性记录了serverCron函数执行的次数,主要用于复制模块中实现“每执行serverCron函数N次就执行一次指定代码”的功能。
网友评论