redis · 3 4 月, 2021 0

REDIS事件

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

  • 文件事件: Redis服务器通过套接字与客户端进行连接, 而文件事件就是服务器对套接字操作的抽象.服务器和客户端的通信会产生响应的文件事件, 而服务器通过监听并处理这些事件来完成一系列网络通信操作。

  • 时间事件: redis服务器中的一些操作需要在给定的时间点执行, 而时间事件就是服务器对这类定时操作的抽象。

文件事件

Redis基于Reactor模式开发了自己的网络事件处理器, 这个处理器被称为文件事件处理器.

  • 文件实践处理器使用I/0多路复用程序来同时监听多个套接字, 并根据套接字目前执行任务来喂套接字关联不同的时间处理器

  • 当被监听的套接字准备好执行连接应答, 读取,写入,关闭等操作时, 与操作相对应的事件就汇产生, 这是文件事件处理器就会调用套接字之前关联好的事件处理器处来处理这些事件。

文件事件处理器的构成

  • 套接字

  • I/0多路复用程序

  • 文件事件分派器

  • 事件处理器

文件事件是对套接字操作的抽象, 每当一个套接字准备好执行连接应答, 写入, 读取, 关闭等操作时, 就会产生一个文件事件. 因为一个服务器通常会连接多个套接字, 所以多个文件事件可能会并发地出现。

I/O多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字.尽管多个文件事件可能会并发地出现, 但I/0多路复用程序总是会将所有产生事件的套接字都放到一个队列里面, 然后通过这个队列, 以有序同步每次一个套接字的方式向文件事件分派器传送套接字. 当上一个套接字产生的事件被处理完毕之后, I/O多路复用程序才会继续向文件事件分派器传送下一个套接字。

I/O多路复用程序的实现

Redis的I/O多路复用程序的所有功能都是通过包装常见的select,epoll,evport,kqueue这些I/0多路复用函数库来实现。

事件的类型

I/O多路复用程序可以监听多个套接字的AE_READABLE事件和AE_WRITABLE事件, 这两类事件和套接字操作之间的对应关系如下:

  • 当套接字变得可读时(客户端对套接字执行write操作, 或者执行close操作), 或者有新的可应答(acceptable)套接字出现时(客户端对服务器监听套接字执行connect操作), 套接字产生AE_READABLE事件.

  • 当套接字变得可写时(客户端对套接字执行read操作), 套接字产生AE_WRITABLE事件。

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

时间事件

时间事件分类

  • 定时事件: 让一段程序在指定的时间之后执行一次。

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

时间事件属性组成

  • id: 服务器为时间事件创建的全局唯一ID(标示号). ID号按从小到大的顺序递增, 新时间的ID号比旧事件的ID号要大

  • when: 毫秒精度的UNIX时间戳, 记录了时间事件的到达时间

  • timeProc: 时间实践处理器, 一个函数. 当事件事件到达时, 服务器就汇调用相应的处理器来处理事件。

时间事件处理器

  • 如果时间事件处理器返回ae.h/AE_NOMORE, 那么这个事件为定时时间: 该事件在达到一次之后就会删除, 之后不再到达

  • 如果事件处理器返回一个非AE_NOMORE的整数值, 那么这个时间为周期时间: 当一个时间事件到达之后, 服务器会根据事件处理器返回的值, 对时间事件的when属性进行更新.让这个事件在一段时间之后再次到达, 并以这种方式一直更新并运行下去。

NOTE: 目前版本REDIS只使用周期性事件, 而没有使用它的定时时间。

实现

服务器将所有时间事件都放在一个无序链表中, 每当时间事件执行器运行时, 它就表里这个链表, 查找所有已经到达的时间事件, 并调用相应的事件处理器。

  • 链表中的所有的事件元素都是按照ID进行排序, 具体的跟when属性无关。

  • 新的事件总是被存放在链表的头部。

NOTE: 在目前版本中, 正常模式下的redis服务器只是用serverCron一个时间事件, 而在benchmark模式中, 服务器也只是用两个时间时间。

时间事件应用实例: serverCron函数

持续运行的redis服务器需要定期对自身的资源和状态检查和调整,从而确保服务器可以长期, 稳定地运行。这些定期操作由redis.c/serverCron函数负责执行, 它的主要工作包含:

  • 更新服务器各类统计信息, 比如时间, 内存占用, 数据库占用情况等

  • 清理数据库中的过期键值对

  • 关闭和清理连接失效的客户端

  • 尝试进行AOF或RDB持久化操作

  • 如果服务器时主服务器, 那么对服务器进行定期同步

  • 如果处于集群模式, 对集群定期同步和连接测试。

Redis2.8开始,用户可以通过修改hz选项来调整serverCron的每秒执行次数.

时间实践调度和执行

事件的调度和执行规则:

  • aeApiPoll函数的最大阻塞时间由到达时间最接近当前时间的时间事件决定,这个方法既可以避免服务器对时间事件进行频繁的轮询, 也已确保aeApiPoll函数不会阻塞过长时间

  • 因为文件事件是随机出现的, 如果等待并处理完一次文件事件之后, 仍未有任何时间事件到达, 那么服务器将字词等待并处理文件事件。随着文件时间的不断执行, 时间会逐渐向时间事件所设置的到达时间逼近, 并最终来到到达事件, 这时服务器就可以开始处理到达时间的事件了。

  • 对文件实践和时间事件的处理都是同步、有序、原子地执行的, 服务器不会中途中断事件处理, 也不会对事件进行抢占, 因此,不管是文件事件的处理器,还是时间事件的处理器, 他们都会尽可能减少程序的阻塞事件,并在有需要时主动让出执行权,从而降低造成事件饥饿的可能性。

    • 命令回复处理器就会主动break跳出写入循环, 将余下的数据留到下次再写

    • 时间时间也会将非常耗时的持久化操作放到子线程或者子进程执行。

  • 因为时间事件在文件事件之后执行, 并且事件之间不会出现抢占, 所以时间事件的实际处理时间,通常会比时间事件设置的到达事件稍晚一些。

重点回顾

  • redis服务器时一个事件驱动程序, 服务器处理的事件分为时间事件和文件事件两类

  • 文件事件处理器是给予Reactor模式实现的网络通信程序

  • 文件事件是对套接字操作的抽象: 每次套接字变为可应答(acceptable), 可写(writable)或者可读(readable)时, 相应的文件事件就会产生

  • 文件事件分为定时事件和周期性事件: 定时事件只在指定的时间到达一次, 而周期性事件则每隔一段时间到达一次

  • 服务器在一般情况下只执行serverCron函数一个时间事件, 并且这个事件是周期性事件

  • 文件事件和时间实践之间是合作关系, 服务器会轮流处理这两种事件, 并且处理事件的过程也不会进行抢占

  • 时间事件的实际处理时间通常会比设定的到达时间晚一些。