Redis服务器是一个事件驱动程序,服务器须要处理如下两类事件:redis
1.文件事件处理器数据库
2.组成
文件事件处理器由四个组成部分:套接字、I/O多路复用程序、文件事件调度器,以及事件处理器;
文件事件是对套接字操做的抽象,每当一个套接字准备好执行链接应答、写入、读取、关闭等操做时,就会产生一个文件事件。由于一个服务器一般会链接多个套接字,因此多个文件事件有可能会并发地出现。
I/O多路复用程序负责监听多个套接字,并向文件时间调度器传送那些产生了事件的套接字。尽管多个文件事件可能会并发地出现,但I/O多路复用程序老是会将全部产生事件的套接字都放到一个队列里面,而后经过这个队列以有序、同步、每次一个套接字的方式向文件事件调度器传送套接字。以下如所示:
服务器
文件事件调度器接收I/O多路复用程序传来的套接字,并根据套接字产生的事件类型,调用相应的时间处理器;时间处理器是一个个函数,它们定义了某个事件发生时,服务器该执行的动做。网络
3.I/O多路复用程序
Redis的I/O多路复用程序的全部功能都是经过包装常见的select、epoll、evport和kqueue这些I/O多路复用函数来实现的,每一个I/O多路复用函数库在Redis源码中都对应一个单独的文件,好比ae_select.c、ae_epoll.c、ae_kqueue.c诸如此类。由于Redis为每一个I/O多路复用函数库都实现了相同的API,因此I/O多路复用程序的底层实现是能够互换,以下如所示:
并发
Redis在I/O多路复用程序的实现源码中用#include宏定义了相应的规则,程序会在编译时自动选择系统中性能最高的I/O多路复用函数库来做为Redis的I/O多路复用程序的底层实现:socket
# ifdef HAVE_EVPORT # include "ae_evport.c" # else # ifdef HAVE_EPOLL # include "ae_epoll.c" # else # ifdef HAVE_KQUEUE # include "ae_kqueue.c" # else # include "ae_select.c" # endif # endif # endif
I/O多路复用程序能够监听多个套接字的ae.h/AE_READABLE事件和ae.h/AE_WRITABLE事件,这两类事件和套接字操做之间的对应关系以下:函数
I/O多路服用程序容许服务器同时监听套接字的AE_READABLE事件和AE_WRITABLE事件,若是一个套接字同时产生了这两种事件,那么文件事件调度器会优先处理AE_READABLE事件,等到AE_READABLE事件处理完成以后,才处理AE_WRITABLE事件。性能
1.API测试
2.文件事件的处理器
链接应答处理器:networking.c/acceptTcpHandler函数是Redis的链接应答处理器,这个处理器用于对链接服务器监听套接字的客户端进行应答,其主要调用anet.c中的anetTcpAccept函数实现,具体实现为sys/socket.h/accept函数的包装。当Redis服务器进行初始化的时候,程序会将这个链接应答处理器和服务器监听套接字的AE_READABLE事件关联起来,当有客户端用sys/socket.h/connect函数链接服务器监听套接字的时候,套接字就会产生AE_READABLE事件,引起链接应答器执行,并执行相应的套接字应答操做;
命令请求处理器:networking.c/readQueryFromClient函数是Redis的命令请求处理器,这个处理器负责从套接字中读入客户端发送的命令请求内容,具体实现为unistd.h/read函数的包装。当一个客户端经过链接应答处理器成功链接到服务器以后,服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联起来,当客户端向服务器发送命令请求的时候,套接字就会产生AE_READABLE事件,引起命令请求处理器执行,并执行相应的套接字读入操做。在客户端链接服务器的整个过程当中,服务器都会一直为客户端套接字的AE_READABLE事件关联命令请求处理器。
命令回复处理器:networking.c/sendReplyToClient函数是Redis的命令回复处理器,这个处理器负责将服务器执行命令后获得的命令回复经过套接字返回给客户端,具体实现为unistd.h/wirte函数的包装。当服务器有命令回复须要传送给客户端的时候,服务器会将客户端套接字的AE_WRITABLE事件和命令回复处理器关联起来,当客户端准备好接收服务器传回的命令回复时,就会产生AE_WRITABLE事件,引起命令回复处理器执行,并执行相应的套接字写入操做;unix
时间事件分为如下两类:
时间事件主要有如下三个属性构成:
一个时间事件是定时事件仍是周期性事件取决于时间处理器的返回值:
1.API
processTimeEvents函数的定义能够用下面的伪代码来描述:
def processTimeEvents(): # 遍历服务器中的全部时间事件 for time_event in all_time_event(): # 检查事件是否已经到达 if time_event.when <= unix_ts_now(): # 事件已到达 # 执行事件处理器,并获取返回值 retval = time_event.timeProc() # 若是这是一个定时事件 if retval == AE_NOMORE: # 那么将该事件从服务器中删除 delete_time_event_from_server(time_event) # 若是这是一个周期性事件 else: # 那么按照事件处理器的返回值更新时间事件的 when 属性 # 让这个事件在指定的时间以后再次到达 update_when(time_event, retval)
2.时间事件应用实例:serverCron函数
持续运行的Redis服务器须要按期对自身的资源和状态进行检查和调整,从而确保服务器可长期、稳定地运行,这些按期操做由redis.c/serverCron函数负责执行,它的主要工做包括:
3.事件的调度与执行
事件的调度和执行由ae.c/aeProcessEvents函数负责,能够用如下源代码完成:
def aeProcessEvents(): # 获取到达时间离当前时间最接近的时间事件 time_event = aeSearchNearestTimer() # 计算最接近的时间事件距离到达还有多少毫秒 remaind_ms = time_event.when - unix_ts_now() # 若是事件已到达,那么remaind_ms的值可能为负数,将它设定为0 if remaind_ms < 0: remaind_ms = 0 # 根据remaind_ms的值,建立timeval结构 timeval = create_timeval_with_ms(remaind_ms) # 阻塞并等待文件事件产生,最大阻塞时间由传入的timeval结构决定 # 若是remaind_ms的值为0,那么aeApiPoll调用以后立刻返回,不阻塞 aeApiPoll(timeval) # 处理全部已产生的文件事件(其实并无这个函数) processFileEvents() # 处理全部已到达的时间事件 processTimeEvents()