Redis设计与实现 第 12 章 事件

第 12 章 事件

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

  • 文件事件:服务器通过套接字与客户端相连,文件事件即服务器对套接字操作的抽象;服务器与客户端的通信会产生相应的文件事件,服务器通过监听和处理事件拉完成一系列网络通信操作
  • 时间事件:服务器中的一些操作需要在给定的时间点执行,时间事件则是对这类定时操作的抽象

12.1 文件事件

Redis 基于 Reactor 模式开发自己的网络时间处理器:文件事件处理器

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

  • 当套接字准备好执行连接应答、读取、写入、关闭等操作时,与之对应的文件事件产生,文件处理器调用套接字之前关联的事件处理器来处理

虽然文件处理器是单线程,但通过 I / O 多路复用监听多个套接字,可以实现了高性能的网络通信模型,又能很好与服务器中单线程的模块进行对接,保证了 Redis 内部单线程的简单性

12.1.1 文件事件处理器的构成

Redis设计与实现 第 12 章 事件

服务器通常连接多个套接字,多个文件事件可能并发地出现,但 I / O 复用总是将所有产生事件的套接字放到一个队列中,有序、同步、每次一个套接字向文件事件分派器传送套接字,只有上一个套接字产生的事件处理完毕(即该套接字为事件所关联的事件处理器执行完毕),才会向文件事件分派器传送下一个套接字

Redis设计与实现 第 12 章 事件

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

所有功能都是通过包装常见的 select、epoll、evport、kqueue 等多路复用函数库,在 Redis 中对应 ae_select.c等

Redis 为每个多路复用函数库都实现了相同的 API,I / O 多路复用程序底层实现是可以互换的

Redis设计与实现 第 12 章 事件

12.1.3 事件的类型

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

  • 套接字变可读(客户端对套接字执行 write 操作或 close 操作),或者有新的可应答(acceptable)套接字出现时(客户端对服务器的监听套接字执行 connect 操作)时,产生 AE_READABLE 事件
  • 套接字变得可写时(客户端对套接字执行 read 操作),套鸡爪子产生 WRITEABLE 事件
  • 可读可写均相对与服务器来说

如果一个套接字同时产生这两种事件,文件分派器会优先处理 AE_READABLE 事件

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

12.1.4 API

ae.c

  • aeCreateFileEvent:套接字描述符、事件类型、事件处理器作为参数,将给定套接字的给定事件加入到 I / O 多路复用程序的监听范围内,并对事件和事件处理器进行关联

  • aeDeleteFileEvent:套接字描述符、事件类型作为参数,让 I / O 多路复用程序取消对给定套接字的给定事件的监听,并取消事件和事件处理器之间的关联

  • aeGetFileEvents:套接字描述符作为参数,返回该套接字正在被监听的事件类型

    • 没被监听:AE_NONE
    • 读事件被监听:AE_READABLE
    • 写事件被监听:AE_WRITEABLE
    • 都被监听:AE_READABLE | AE_WRITEABLE
  • aeWait:套接字描述符、事件类型、毫秒数为参数,在给定的时间内阻塞并等待套接字的给定类型事件的产生,当事件产生成功或者等待超时则返回

  • ae.ApiPoll:sys/time.h/struct timeval 作为参数,在指定时间内,阻塞并等待所有被 aeCreateFileEvent 设置为监听状态的套接字产生的文件事件,当有文件事件产生或者等待超时则返回

  • aeProcessEvents:文件事件分派器,调用 aeApiPoll 等待事件,再遍历已产生事件,调用响应事件处理器处理

  • aeGetApiName:返回 I / O 多路复用程序底层所使用的 I / O 多路复用函数库的名称

12.1.5 文件事件的处理器

  • 连接应答处理器:对连接服务器的各个客户端进行应答
  • 请求处理器:接收客户端传来的命令请求
  • 回复处理器:向客户返回命令的执行结果
  • 复制处理器:当主服务器和从服务器进行复制操作

1.连接应答处理器

networking.c/acceptTcpHandler 函数是 Redis 的连接应答处理器,为 sys/socket.h/accept 函数的包装

初始化时,此处理器会和服务器监听套接字的 AE_READABLE 事件关联,当有客户端用 sys/socket.h/connect 函数连接服务器监听套接字时,套接字产生 AE_READBALE 事件,引发连接应答处理器执行

2.命令请求处理器

networking.c/readQueryFromClient 是命令请求处理器,负责从套接字中读入客户端发送的命令请求内容,为 unistd.h/read 函数的包装

当客户端已经通过连接应答处理器连接到服务器之后,此处理器会和服务器监听套接字的 AE_READABLE 事件关联,当客户端发送命令请求后,套接字产生 AE_READBALE 事件,引发命令请求处理器执行

3.命令回复处理器

networking.c/sendREplyToClient 为命令回复处理器,为 unistd.h/write 的包装

当服务器有命令回复给客户端时,AE_WRITEABLE 事件和命令回复处理器关联

命令回复完毕之后会解除关联

4.一次完整的客户端与服务器连接事件示例

假设 Redis 服务器在运行,监听套接字的 AE_READABLE 事件在监听下,对应处理器为连接应答处理

如果一个 Redis 客户端向服务器发起连接,监听套接字会产生 AE_READABLE 事件,触发连接应答处理器执行,处理器会对请求进行应答,然后创建客户端套接字,以及客户端状态,并将 AE_READABLE 事件与命令处理器关联,使得客户端可以向主服务器发送命令请求

客户端向服务器发送命令请求,客户端则产生 AE_READABLE 事件,引发命令请求处理器执行,处理器读取了客户端的命令内容,传给相关程序去运行

执行命令产生了命令回复,服务器将客户端套接字的 AE_WRITEABLE 事件与命令回复处理器关联,客户端进行读取产生此事件,命令回复处理器将内容写进套接字,服务器最后解除客户套接字的 AE_WRITABLE 事件与命令回复处理器之间的关联

Redis设计与实现 第 12 章 事件

12.2 时间事件

Redis 时间事件分为:

  • 定时事件:指定时间运行一次
  • 周期事件:每隔一段时间执行一次

时间事件三个属性组成:

  • id:时间事件全局唯一 ID,递增
  • when:毫秒精度的 UNIX 时间戳,时间事件的到达时间
  • timeProc:时间处理器,函数,处理事件

时间事件的返回值决定时间事件的分类:

  • ae.h/AE_NOMORE:定时事件,在到达一次之后就会被删除,不再到达
  • 非 AE_NOMORE 的整数值:周期性事件,时间事件到达之后,会根据返回值更新 when 属性,一直循环下去

Redis 3.0 只使用 周期性事件,无定时事件

12.2.1 实现

所有时间事件存放在无序链表中,时间事件执行器遍历整个链表,查找已经到达的时间事件,调用相应的事件处理器

无序指when属性无效,链表实际按 ID 排序

3.0 版本,正常模式下的 Redis 服务器只使用 serverCron 一个时间事件,在 benchmark 模式下,也只使用两个时间事件,这种情况下无序链表的长度很短,不会影响事件执行性能

12.2.2 API

ae.c

  • aeCreateTimeEvent:毫秒数,时间事件处理器为参数,将新的时间事件添加到服务器

  • aeDeleteFileEvent:时间事件 ID ,删除对应的时间事件

  • aeSearchNearestTimer:返回到达时间距离当前时间最接近的时间事件

  • processTimeEvents:时间事件的执行器,遍历所有已到达的时间事件,调用事件的处理器

    • 已到达:when 属性的时间戳小于等于当前时间的时间戳

    • 伪代码:

      Redis设计与实现 第 12 章 事件

12.2.3 时间事件应用实例:ServerCron 函数

redis.c/serverCron :定期对服务器自身资源和状态进行检查和调整,确保服务器长期、稳定地运行

  • 更新服务器的各类统计信息:时间、内存占用、数据库占用等

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

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

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

  • 主服务器需要对从服务器进行定期同步

  • 集群模式:对集群进行定期同步和连接测试

周期性事件运行 serverCron,hz 选项调整 serverCron 每秒执行次数

12.3 事件的调度与执行

ae.c/aeProcessEvents 函数复杂调度文件事件和时间事件

Redis设计与实现 第 12 章 事件

将 aeProcessEvents 放在一个主循环中,再加上初始化和清理函数,构成了简化的 Redis 服务器的主函数

Redis设计与实现 第 12 章 事件

Redis设计与实现 第 12 章 事件

事件调度和执行规则:

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

  2. 文件事件是随机的,如果等待并处理完文件事件后没有时间事件到达,则再次等待并处理文件事件,不断执行直到时间事件到到达时间,则处理时间事件

  3. 对两种事件的处理都是同步、有序、原子地,不会中断、其他事件抢占,尽可能地减少阻塞时间,并在有需要让出执行权,避免饥饿;如命令回复写入客户端套接字数据超过预设量则使用 break 跳出循环,等待下次;时间事件则将耗时的持久化操作放在子进程、子线程执行

  4. 因为时间事件在文件事件之后执行,且事件之间不抢占,所以实际 的时间事件处理时间是稍晚的

Redis设计与实现 第 12 章 事件

上一篇:设计模式总结:结构型模式


下一篇:PKM图片转带透明通道的PNG图片