《Redis设计与实现》读书笔记(十六) ——Redis文件事件
(原创内容,转载请注明来源,谢谢)
一、概述
redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:
1)文件事件(fileevent),redis服务器与客户端通过socket连接,文件事件是对socket的抽象,服务器与客户端通信会产生文件事件,服务器通过监听文件事件产生一系列操作。
2)时间事件(timeevent),redis的部分操作需要定时执行,主要是serverCron函数,例如定时清理过期键、定时aof写入等,时间事件是服务器对此类的抽象。
二、文件事件
1、reactor模式
redis基于reactor模式开发网络事件处理器,将其称之为文件时间处理器。该处理器通过I/O多路复用,同时监听多个socket,并根据socket当前处理的任务来关联不同的事件处理器。
reactor模式如下图所示:
其是处理并发I/O比较常见的一种模式,用于同步I/O。中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程阻塞在多路复用器上。一旦有I/O事件到来或是准备就绪(区别在于多路复用器是边沿触发还是水平触发),多路复用器返回并将相应I/O事件分发到对应的处理器中。
reactor中文称为反应器,即其不是等套接字来调用,而是提前建立好,并主动去调用到来或就绪的套接字。
当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作,会产生相应的文件事件。此时,文件事件处理器(即reactor)会调用之前关联好的处理器来处理事件。
I/O多路复用的reactor模式,使得redis虽然是单线程处理,但是仍然具有高效率。
2、文件事件处理器的构成
文件事件处理器由四部分组成——套接字、I/O多路复用程序、文件事件分派器、事件处理器,如下图所示:
通常,一个服务器需要同时处理多个套接字,因此文件事件可能并发出现。I/O多路复用程序是通过监听多个套接字,并将准备好的套接字按准备好的时间顺序转发给文件时间分派器。由文件时间分派器根据具体的事件类型,分派给不同的事件处理器。
I/O多路复用程序将并发出现的多个套接字加入到队列中,以有序、同步、每次一个的方式,将事件发送给文件事件分派器,并且当事件处理完毕后,才会将下一个事件发送过去。如下图所示:
I/O多路复用程序会监听多个套接字的读(ae.h/AE_READABLE)和写事件(ae.h/AE_WRITEABLE),当套接字可读或有新的可应答的套接字出现,产生读事件;当套接字可写,产生写事件。如果同时可读可写,则会先读再写。如果没有任何事件,返回的是ae.h/AE_NONE。
3、I/O多路复用程序
redis的I/O多路复用程序是通过包装操作系统原生的如select、epoll、evport、kqueue等I/O多路复用函数库,来实现I/O多路复用。由于redis底层对每种I/O多路复用都实现了相同的api接口,因此可以根据实际情况互换。每种复用方式,在redis里面是分别保存在一个.c的文件内。
1)select
select是较早的unix多路复用方式,其提供三种流——读、写、异常,调用时需要传入感兴趣的流,select将其拷贝到内核。select会使程序阻塞,让后其轮询每个流,当某个监听的流有操作时,其调用相关函数,返回结果。
由于select会将传入的流修改,并且需要全量传入、全量返回,对于大量的请求下,效率较低。
2)epoll与kqueue
epoll是linux内核的,kqueue是BSD内核的,其原理基本一致。其是通过创建一个句柄,并注册事件函数。即其是提前将有可能的事件都先注册好,当具体事件发生时去调用,而不是select的每次发生时在注册。
4、文件事件处理器
redis针对不同的文件事件,编写了多个文件事件处理器,包括处理各个客户端连接的应答处理器、接收客户端请求的命令请求处理器、向客户端返回命令结果的命令回复处理器、主从复制情况下的复制处理器。
1)应答处理器
名称是networking.c/acceptTcpHandler,用于对连接服务器监听套接字的客户端进行应答。redis初始化的时候,会将此应答处理器和套接字的读事件联系起来。当客户端发送连接请求,就会产生读事件(AE_READABLE)。
2)命令请求处理器
名称是networking.c/readQueryFromClient,用于处理读入客户端发送过来的套接字,当应答处理器连接到套接字的时候,命令请求处理器就会将读事件与其关联起来。当客户端发送请求时,就会产生读事件,命令请求处理器读入套接字中客户端发送的命令事件。
3)命令回复处理器
名称是networking.c/sendReplyToClient,用于将服务器执行命令后得到的回复返回给客户端。当有命令要返回给客户端,redis会将写事件与命令回复处理器关联起来,当客户端准备好接受服务器的回复,就会产生写事件,引发命令处理器将相关要返回给客户端的实际写入套接字。命令发送完毕后,会解除写事件和该客户端的关联。
4)示例
下面讲述一次完整的客户端与服务器连接事件。
当redis服务器正常运作时,监听套接字的事件AE_READABLE处于监听状态,且相应处理该事件的是应答处理器。
当有客户端向redis服务器发送连接请求,会产生AE_READABLE,触发应答处理器执行。处理器会进行连接并回复客户端,并创建客户端套接字,将套接字的AE_READABLE与命令请求处理器关联。
当客户端向redis服务器发送命令,会产生AE_READABLE事件,命令请求处理器会读入套接字中的命令,并传给相关执行程序去执行。
redis服务器执行完毕命令后,将产生相应的回复,服务器会将套接字的AE_WRITEABLE与命令回复处理器关联,当客户端尝试读取回复,客户端套接字将产生AE_WRITEABLE,命令回复处理器将执行,把要返回的内容写入套接字。
整个过程如下图:
——written by linhxx 2017.09.06