redis服务器就是一个事件驱动程序,服务器需要处理两类事件:

  • 文件事件(file-event):
    • redis服务器通过套接字与客户端进行连接。
    • 文件事件就是服务器对于套接字操作的抽象。
    • 服务器与客户端的通信会产生相应的文件事件
    • 服务器通过监听并处理这些事件来完成一系列网络通信操作
  • 时间事件(time-event):
    • redis服务器中的一些操作(比如serverCron函数)需要在给定时间点执行
    • 时间事件就是服务器对于这类定时操作的抽象

文件事件

redis给予reactor模式开发了自己的网络事件处理器:文件事件处理器(file-event-handler)

  • 文件事件处理器使用I/O多路复用程序来同时监听多个套接字;文件事件处理器会根据目前执行的任务来为套接字关联不同的事件处理器
  • 文件事件处理器以单线程方式运行,通过使用I/O多路复用来监听多个套接字
    • 既可以很好的以单线程方式与redis服务器中其他模块对接
    • 又可以利用多路复用机制保障了高性能的网络通信

文件事件处理器构成

  • 套接字
  • I/O多路复用程序
  • 文件事件分派器
  • 事件处理器

  •  文件事件(file-event)是对套接字的操作抽象
    • 每当一个套接字准备好执行accept,write,read.close等操作时,就会产生一个文件事件
    • 一个服务器会有多个套接字,所以文件事件会并发出现
  • I/O多路复用程序负责监听套接字并且向文件事件分派器派送产生了事件的套接字
    • I/O多路复用程序会将并发的套接字维护到一个有序同步队列
    • 顺讯的one-by-one的方式向事件分派器传送套接字
    • 当套接字的文件事件被执行完毕,多路复用程序才会继续向文件事件处理器派送下一个套接字
  • 文件事件处理器接受多路复用程序传来的套接字,并且根据套接字的事件类型调用相应事件处理器
  • 服务器回味执行不同任务的套接字冠梁不同的事件处理器;这些处理器都是一个个函数,定义了事件发生时服务器该执行的动作

I/O多路复用程序实现

redis的多路复用功能都是通难过包装常见的select,epoll,ecport和kqueue这些I/O多路复用库来实现的,每个I/O多路复用函数库在redis源码中都是一个单独的文件

redis为每个I/O多路复用函数库都实现了相同的API,所以I/O多路复用程序的底层都收可以互换的,程序会自动选择系统中性能最高的I/O多路复用函数来作为redis的I/O多路复用程序底层实现

 

 

文件事件类型

I/O多路复用程序可以监听多个套接字的ae.h/AE_READABLE事件和ae.h/AE_WRITABLE事件

  • 当套接字变得可读时套接字,产生AE_WRITABLE事件
    • 客户端对套接字执行write操作-(向redis服务器写数据)
    • 客户端对套接字执行执行close操作-(关闭连接)
    • 当新的accept套接字出现时(客户端对服务器的监听套接字执行connect操作)
  • 当套接字变得可写时(客户端对套接字执行read操作)

多路复用程序允许服务器同时监听套接字的AE_READABLE事件和AE_WRITABLE事件;如果一个套接字同时产生两种事件,那么文件事件分派器会优先处理AE_READABLE事件,等到AE_READABLE事件处理完成后,才处理AE_WRITABLE事件

如果一个套接字又可读又可写,服务器先读后写

文件事件api

  • ae.c/aeCreateFileEvent函数接受一个套接字描述符,一个事件类型以及一个事件处理器作为参数,将给定套接字的给定事件加入到I/O多路复用和程序的监听范围之内
  • ae.c/aeDeleteFileEvent函数接受一个套接字描述符,一个事件类型以及一个事件处理器作为参数,将给定套接字的给定事件取消I/O多路复用和程序的监听,并且取消事件和事件处理器之间的关联
  • ae.c/aeGetFileEvents函数接受一个套接字描述符,返回该套接字正在被监听的事件类型:
    • 如果套接字没有被监听,返回AE_NONE
    • 如果套接字读事件正在被监听,返回AE_READABLE
    • 如果套接字写事件正在被监听,返回AE_WRITABLE
    • 如果套接字读事件和写事件都正在被监听,返回AE_READABLE | AE_WRITABLE
  • ae.c/aeWait函数接受有个套接字描述符,一个事件类型,一个毫秒数作为参数,在给定时间内阻塞并等待套接字的给定事件发生,当事件成功发生或者等待超时后,函数返回
  • ae.c/aeApiPoll函数接受一个sys/time.h/struct timeval结构为参数,并且在指定时间内,阻塞并等待所有被aeCreateFileEvent函数社市委监听状态的套接字产生文件事件,当文件事件产生,或者等待超时,函数返回
  • ae.c/aeProcessEvents函数时文件事件分派器,他先调用aeApiPoll函数来等待文件事件,然后遍历所有已产生的事件,并且调用相应的事件处理器来处理事件
  • ae.c/aeGetApiName函数返回I/O多路复用程序底层使用的I/O多路复用函数名称:epoll:表示底层为epoll,函数诸如此类

文件事件处理器

  • 连接应答处理器
    • networking.c/acceptTcpHandler函数时redis的应答处理器
    • 这个处理器用于对连接服务器监听套接字的客户端进行应答
    • 具体实现为:sys/socket.h/accept函数的包装
    • 当redis服务器初始化的时候,程序会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来,当有客户端调用sys/socket.h/connect函数连接服务器监听套接字的时候,套接字就会产生AE_READABLE事件,引发连接应答处理器执行
  • 命令请求处理器
    • networking.c/readQueryFromClient函数时redis的命令请求处理器
    • 这个处理器负责从套接字中读入客户端发送的命令请求内容
    • 具体实现为unistd.h/read函数的包装
    • 当一个客户端通过连接应答处理器成功连接服务器后
    • 服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联起来
    • 当客户端想服务器发送命令请求时,套接字结汇产生AE_READABLE事件
    • 引发命令请求处理器执行并执行相应的套接字读入操作
  • 命令回复处理器
    • networking.c/sendReplyToClient函数时redis的命令回复处理器
    • 这个处理器负责将服务器执行命令会得到的命令回复通过套接字返回给客户端
    • 具体实现为unistd.h/write函数的包装
    • 当服务器有命令回复需要传送给客户端的时候
    • 服务器会将客户端套接字的AE_WRITEABLE事件和命令回复处理器关联起来
    • 当客户端准备好接受服务器传回的命令回复时就会产生AE_WRITEABLE事件,引发命令回复处理器执行套接字写入操作

时间事件

一个时间事件主要由三个属性构成:

  • id:服务器为时间事件创建的全局唯一自增id
  • when:毫米精度的unix时间戳,记录时间事件到达时间
  • timeProc:时间事件处理器,一个函数,当时间事件到达时,服务器会调用相应的处理器来处理事件

时间事件的分类:

  • 定时时间事件
  • 周期时间事件

redis目前只有周期事件,无定时事件

时间事件实现

服务器将所有时间事件都存放在一个无序链表(按照id有序,when无序)中,每当时间事件执行器运行,它就遍历整个链表查找所有已到达的时间事件,并调用相应时间事件处理器

 

当一个时间事件到达,服务器会根据事件处理器返回的值,对时间事件的when属性做更新,让这个事件在一段时间后再次到达,并且以这种方式一直更新并运行下去

正常模式下redis服务器只使用serverCron这么一个时间事件!!!

时间事件API

  • ae.c/aeCreateTimeEvent函数接受一个毫秒数millsseconds和一个时间事件处理器proc作为参数,讲一个新的时间事件添加到服务器,这个新的时间事件在当期事件的millseconds毫秒之后到达,而事件处理器为proc
  • ae.c/aeDeleteFileEvent函数接受一个时间事件id为参数删除这个时间事件
  • ae.c/aeSearchNearestTimer函数返回到达事件距离当前时间最接近的那个时间事件
  • ae.c/processTimeEvents函数时事件之间的执行器,这个函数会遍历所有已到达的时间事件,并且调用这些事件的处理器

serverCron函数

serverCron函数负责定期对redis服务器资源以及状态做检查和调整,从而确保redis服务器可以长久稳定的运行,serverCron主要负责:

  • 更新服务器各类统计信息,比如事件,内存占用,数据库占用等
  • 清理出具库中过期键值对
  • 关闭和清理连接失效的客户端
  • 尝试进行AOF或者RDB持久化操作
  • 如果是主服务器,那么会定期执行主从同步
  • 如果是集群,对集群进行定期同步和连接测试

在redis2.6版本,服务器默认固定serverCron每秒运行10次,平均间隔100毫秒运行一次

在redis2.8开始,用户可以通过修改redis.cnf中hz选项调整serverCron每秒执行次数

  • hz 10
    • redis对于hz的值范围建议在1-500之间,默认值为10
    • Redis建议hz的值不要超过100
  • dynamix-hz yes
    • redis对于是否使用hz选项的开关,默认yes打开

事件的调度与运行

redis服务器中同时存在文件事件和时间事件两种事件类型,所以redis服务器必须对这两种事件进行调度,决定何时来处理事件以及花多少时间来处理事件,事件的调度由ae.c/aeProcessEvents函数负责;将aeProcessEvents函数置于一个循环里面,加上初始化和清理函数,就构成了redis服务器的主函数(伪代码):

 

 

事件处理的角度redis服务器的运行流程:

 

 

事件调度和执行规则:

  1. aeApiPoll函数的最大阻塞时间由到达时间最接近当前时间的时间事件决定;这种机制可以避免服务器对时间事件进行频繁轮询(忙等待)也可以确保aeApiPoll函数不会阻塞过长时间
  2. 文件事件是随机出现的,如果等待并完成一次文件事件之后仍未有时间事件到达,那么服务器将再次等待并处理文件事件;随着文件事件的不断执行,时间会向时间事件所设置的到达时间逼近,当时间事件最终到达触发事件,此时就开始处理时间事件
  3. 对于文件事件和时间事件的执行处理都是同步,有序,原子的执行,服务器不会中途中断去执行其他类型的事件的,也不会对事件进行抢占。所以不管文件事件处理器还是时间事件处理器都会尽可能降低程序阻塞时间并且在需要时主动让出执行权,从而降低事件饥饿的可能性
  4. 因为时间事件总是文件事件之后执行,并且事件之间不会出现抢占,所以时间事件的执行处理时间通常会必时间事件设定的到达时间晚一点点

 

 

 

版权声明:本文为cavaliersyb原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/cavaliersyb/p/12010007.html