民工兄Redis教程(五):事件机制详解
前言
Redis采用事件驱动机制
来处理大量的网络IO。它没有使用像libevent或libev这样的高级开源解决方案,而是实现了一个非常简单的事件驱动的ae_event库。
什么是事件驱动?
所谓事件驱动,简单来说就是你点击什么按钮(即产生什么事件),计算机执行什么操作(即调用什么函数)。当然,事件不限于用户操作。事件自然是事件驱动的核心。
从事件的角度来看,事件控制器程序的基本结构由事件收集器、事件发送器和事件处理器组成。
- 事件收集器具体负责收集所有事件,包括来自用户的事件(如鼠标事件、键盘事件等)、来自硬件的事件(如时钟事件等)、来自软件的事件(如操作系统、应用程序本身等。)。
- 事件分发器负责将收集器收集到的事件分发到目标对象。
- 事件处理程序负责响应事件的特定工作。
事件驱动库代码主要在src/ae.c中实现,其用途如下。
Redis 服务器是一个事件处理程序。服务器必须处理以下两类事件:
File事件(文件事件):Redis服务器通过socket和文件事件连接到客户端(或另一个Redis服务器),它是服务器socket操作的抽象。服务器与客户端(或其他服务器)之间的通信会产生相应的文件事件,服务器通过监听并处理这些事件来完成一系列的网络通信操作。
Time Event(时间事件):Redis服务器上的一些操作必须在给定的时刻执行,时间事件是服务器端对这些预定操作的抽象。
文件事件
Redis 基于 Reactor 模式开发了自己的网络事件处理器: 该处理器称为文件事件处理程序:
- 文件事件处理程序使用 I/O 复用程序来复用侦听更多套接字相同的时间,并根据套接字当前执行的任务为套接字分配不同的时间处理程序
- 当受监控的套接字准备连接时,当执行接收、读取、写入、关闭等操作时,文件事件将会生成对应的操作。此时,文件处理器调用套接字并将其分配在事件处理程序前面来处理这些事件
虽然文件事件处理程序以单线程方式运行,但使用 I/O 多路复用器来监听多个套接字,文件事件处理程序可以实现既高性能又强大的网络通信模型,与Redis服务器上同样以单线程方式运行的其他模块很好地配合,保留了Redis内部单线程设计的简单性。更多学习Redis的文章请参见:NoSQL数据库系列-Redis。该系列持续更新。
文件事件处理器的组成
文件事件处理器主要由四部分组成。它们是套接字、I.O 多路复用器、文件事件调度程序和事件处理程序。
- 文件事件是套接字操作的抽象。每当套接字准备好响应连接(接受)、写入、读取、关闭和其他操作时,就会生成文件事件。由于服务器通常连接到多个套接字,因此多个文件事件可能会同时发生。 I/O 多路复用器负责侦听多个套接字并将这些生成的事件传递给文件的事件调度程序。事件套接字
- 即使多个文件事件可能同时发生,I/O 多路复用器始终将所有事件生成套接字排队,然后将其转发。队列按顺序、同步地将套接字传递到文件事件调度程序,一次一个套接字。当上一个socket产生的事件处理完毕后,I/O多路复用器会继续将下一个socket发送给文件事件调度器
- 文件事件调度器接受I/O多路复用器程序携带的socket使用,根据socket产生的事件类型调用相应的时间处理程序
I/O复用程序实现
Redis I/O复用所有程序功能均使用select等公共库I/O复用函数实现、 epoll、evport 和 kqueue。每个 I/O 多路复用函数库对应于 Redis 源代码中的一个单独函数。文档。
下图是基于复用的Redis IO模型。 因为Redis为每个I/O复用函数库实现了相同的API,所以I/O复用程序的基本实现是可以互换的。
事件类型
I/O 多路复用器可以监听多个套接字的 READABLE 和 WRITEABLE 事件。这两个事件和socket操作的对应关系如下:
- 当socket变为可读时(客户端对socket执行写操作或执行关闭操作),或者当出现新的可接受的socket时(客户端执行监听服务器上的套接字(主席连接操作),套接字生成 READABLE 事件
- 当套接字变为可写时(客户端对套接字执行读操作),套接字生成 WRITEABLE 事件
I/O 复用应用程序允许服务器同时侦听套接字的 READABLE 和 WRITEABLE 事件。如果套接字同时生成两个事件,则文件的事件调度程序首先处理 READABLE 事件。处理完 READABLE 事件后,将处理 WRITEABLE 事件。更多学习Redis的文章请参见:NoSQL数据库系列-Redis。该系列持续更新。
API
aeCreteFileEvent 函数接受套接字描述符、事件类型和时间处理程序作为参数,将给定的套接字事件添加到帧中 I/O 多路复用器的侦听范围,并将事件和时间处理程序关联起来。
aeDeleteFileEvent 函数接受套接字描述符和侦听器事件类型作为参数,允许 I/O 多路复用器取消给定套接字的给定事件侦听器并取消事件与时间处理程序之间的通信。连接。
aeGetFileEvents 函数接受套接字描述符并返回套接字正在侦听的事件类型:
- 如果套接字没有要侦听的任务事件,则该函数返回 AE_NONE。
- 如果正在监听socket读事件,函数返回AE_READABLE。
- 如果监听到socket写事件,则函数返回AE_WRITABLE
- 如果监听到socket读写事件,则函数返回AE_READABLE|AE_WRITEABLE。
文件事件处理器
Redis 已经为文件事件编写了多个处理器。这些事件处理程序用于实现各种网络通信要求:
- 为了响应连接到服务器的每个客户端,服务器 A 的响应处理程序将自身与侦听套接字关联。
- 为了接收来自客户端的命令请求,服务器必须向客户端的套接字分配命令请求处理程序。
- 为了将命令执行的结果返回给客户端,服务器必须为客户端套接字分配一个命令响应处理器。
- 当主服务器和从服务器执行复制操作时,两个从服务器都必须连接到一个专门为复制功能编写的命令响应处理器。复制处理函数
连接响应处理程序
networking.c/accpetTcpHandler 是 Redis 连接响应处理程序。该处理器用于响应连接到侦听服务器套接字的客户端。具体实现是sys/socket.h/accpet函数包装器。
当Redis服务器初始化时,程序将此连接响应处理器绑定到监听服务器套接字的AE_READABLE事件。当客户端使用 sys/socketh/connect 函数连接到侦听套接字的服务器时,套接字会生成 AE_READABLE 事件,该事件会导致连接响应处理器运行。
命令请求处理器
networking.c/readQueryFromClient 该函数是 Redis 命令处理器。该命令处理器负责从套接字读取客户端发送的命令请求的内容。具体实现是 unistd.h/read 函数包装器。
当客户端通过连接响应处理器成功连接到服务器时,服务器会将 AE_READABLE 事件分配给命令请求处理器的客户端套接字。当客户端向服务器发送命令请求时,将产生一个套接字事件。 AE_READABLE事件导致命令请求处理器执行并执行相应的套接字读操作。
在客户端连接到服务器的整个过程中,服务器始终会为客户端套接字的 AE_READABLE 事件分配一个命令请求处理器。
命令响应处理器
sendReplyToClient 函数是 Redis 命令响应处理器。该处理器负责将服务器执行完命令后收到的响应命令通过socket返回给客户端。
当服务器有命令响应要发送到客户端时,服务器将客户端套接字的 AE_WRITEABLE 事件分派到命令处理器。当客户端准备好接受服务器发回的命令响应时,就会发生这种情况。 AE_WRITABLE事件触发命令响应处理器的执行,并执行相应的套接字写操作。
当发送命令响应处理程序时,服务器会取消命令响应处理程序与套接字的 AE_WRITABLE 事件的关联。更多学习Redis的文章请参见:NoSQL数据库系列-Redis。该系列持续更新。
客户端与服务器连接完成事件
当Redis服务器运行时,该服务器的ship socket的AE_READABLE事件应该处于监听状态,该事件对应的处理器是连接响应处理器。
当 Redis 客户端发起与服务器的连接时,ship 套接字会生成 AE_READABLE 事件,触发连接响应处理器执行。处理器响应客户端的连接响应请求,然后创建客户端套接字和客户端状态,并将客户端套接字的AE_RAEDABLE事件分配给命令请求处理器,以便客户端可以主动请求服务器发送命令。
然后,假设客户端向主服务器发送命令请求,客户端套接字将生成 AE_READABLE 事件,该事件将触发命令处理器的执行。处理器读取客户端命令的内容,然后将其传递给适当的程序。工具。
命令执行会产生相应的命令响应。为了将这些命令响应传递给客户端,服务器将客户端套接字的 AE_WRITABLE 事件分配给命令响应处理程序。当客户端尝试读取对命令的响应时,客户端套接字会生成 AE_WRITABLE 事件,该事件启动命令响应处理器的执行。当命令响应处理器将所有命令响应写入套接字时,服务器释放客户端。套接字的 AE_WRITABLE 事件和命令响应处理程序之间的关联。
时间事件
Redis时间事件可以分为两类:
- 时间时间:让程序在指定的时间后运行
- 定期时间:让程序在指定的时间运行事件主要包括三个属性:
- id:服务器为时间事件生成的全局唯一 ID(标识号)。识别号从小到大依次增加11。新事件的ID号大于旧事件的ID号
- when:毫秒精度的UNIX时间戳,记录时间事件的到达时间
- timeProc:处理时间事件的设备,函数。当定时事件到来时,服务器调用相应的处理器来处理该事件
该定时事件是定时事件还是周期事件取决于定时事件处理器的返回值。
- 如果事件处理程序返回 ae.h/AE_NOMORE ,则该事件是定时事件:该事件到达一次后将被删除,不会再次到达
- 如果事件处理程序返回 AE_NOMORE 以外的整数值,那么这个事件就是一个周期性事件:当定时事件到来时,服务器根据事件处理程序返回的值更新定时事件的when属性,使得该事件在一段时间后再次到来,并以这种方式继续更新并运行。
Redis中时间事件的使用场景
时间事件的主要应用是redis服务器需要定期调度自身的资源和配置,以保证服务器的长期运行。这些操作是由serverCron函数实现的。该函数执行以下操作。
- 1。更新redis服务器的各种统计信息,包括时间、内存使用情况、数据库使用情况等。
- 2.清除数据库中过期的密钥(过期删除)
- 3.关闭并清理连接失败的客户端
- 4。尝试aof rdb的持久化操作(数据持久化)
- 5。如果该服务器是主服务器,则会定期与从服务器同步数据(主从复制)。
- 6。如果是集群模式,请定期对集群进行同步和连接测试(健康检查)。
启动Redis后,serverCron函数会定时运行,直到redis终止。默认情况下,每秒执行 10 次,即每 100 毫秒执行一次。
您可以使用redis配置文件(redis.conf)中的hz选项来调整执行频率。
#redis执行任务的频率为1s除以hz, 一秒钟执行多少次 hz 10
实现
服务器将所有时间事件放入无序链表中。每当定时器事件触发器被触发时,它都会遍历整个链表,找到所有已到达的定时器事件,并引发相应的事件。处理器时间事件的链表
是一个无序链表。这并不是说链表不按ID排序,而是说Lina表不按when属性的大小排序。因为链表不是按when属性排序的,所以当时间事件触发器触发时,它必须循环链表中的所有时间事件,以确保服务器上的所有时间事件都得到处理。更多学习Redis的文章请参见:NoSQL数据库系列-Redis。该系列持续更新。
时间事件应用示例:serverCron函数
持续运行的Redis服务器需要定期检查和调整自身的资源和状态,以保证服务器能够长期稳定运行。这些常规操作包括 redis.c/serverCron 函数负责执行。它的主要工作是:
- 更新服务器的各种统计信息,如时间、内存使用情况、数据库使用情况
- 清理数据库中过期的键值对
- 关闭并清理失败的连接 客户端
- 尝试执行 AOF 或 RDB 持久化操作
- 如果服务器是主服务器,则在子服务器上定期进行同步和连接测试
- 如果是集群模式,则在集群上定期进行同步和连接测试
Redis服务器以周期性事件的形式运行serverCron函数。当服务器运行时,serverCron 会每隔一段时间运行一次,直到服务器关闭。
事件调度与执行
因为服务器上同时存在文件事件和时间事件,所以服务器必须对这两个事件进行调度,决定什么时候处理文件事件,什么时候处理时间事件,处理多少个事件events 用于事件处理。
事件的调度和执行由 ae.c/aeProcessEvents 函数处理。将 aeProcessEvents 函数放入一个循环中,加上初始化和清理函数,构成 Redis 服务器的主要函数。下面是该函数的伪代码:
def main(): #初始化服务器 init_server() #一直处理事件,知道服务器关闭 while server_is_not_shutdown(): aeProcessEvents() #服务器关闭,执行清理操作 clean_server()
从事件处理的角度来看,可以利用Redis服务器的运行过程。它的事件处理机制起着非常重要的作用。所选模型为反应堆模型。多个线程能做的事情让单个线程做,不存在线程安全问题。来源:https://blog.csdn.net/weixin_43809223/article/details/109631305
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。