Skip to content

Latest commit

 

History

History
160 lines (106 loc) · 7.25 KB

09 事件与并发.md

File metadata and controls

160 lines (106 loc) · 7.25 KB

事件

0 概述

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

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

1 文件事件

原理

Redis基于Reactor模式开发了自己的网络事件处理器,称为文件事件处理器,文件事件处理器以单线程方式运行。

Redis 基于Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。

文件事件处理器的四个组成部分:

  • 套接字。当被监听的套接字准备好执行accept、read、write、close等操作时,与操作相对应的文件事件就会产生。
  • I/O多路复用程序。使用I/O多路复用程序同时监听多个套接字,并向文件分派器传送那些产生了事件的套接字(使用队列)。
  • 文件事件分派器。根据套接字的事件类型,调用相应的事件处理器。
  • 事件处理器

实现

  • Redis的I/O多路复用包装了常见的select、poll、evport和kqueue等函数库来实现的,每个函数库的在Redis源码中都有一个独立的文件。

事件的类型

  • I/O多路复用程序可以监听多个套接字的ae.h/AE_READABLE和ae.h/AE_WRITABLE事件。两种事件可以同时监听,但会优先处理AE_READABLE事件。

API

函数 参数 作用
ae.c/aeCreateFileEvent 套接字描述符、事件类型、事件处理器 将给定套接字的给定事件加入到I/O多路复用程序的监听范围之内,并对事件和事件处理器进行关联
ae.c/aeDeleteFileEvent 套接字描述符、监听事件类型 让I/O多路复用程序取消套接字的事件监听,并取消事件与事件处理器的关联
ae.c/aeGetFileEvent 套接字描述符 返回套接字正在被监听的事件类型,none、readable、writable
ae.c/aeWait 套接字描述符、事件类型、毫秒数 在给定时间内阻塞并等待套接字的给定类型的事件发生
ae.c/aeApiPoll timeval结构 在给定事件内,阻塞并等待所有被acCreateFileEvent函数设置为监听状态的套接字产生文件事件
ae.c/aeProcessEvents 先调用aeApiPoll来等待事件,然后遍历所有事件,调用相应的事件处理器
ae.c/aeGetApiName 返回I/O多路复用程序底层使用的函数库名称:epoll、select等

文件事件的处理器

Redis为文件事件编写了多个处理器,分别用于实现不同的网络通信需求:

  • 连接应答处理器:监听客户端的套接字,并应答。

    networking.c/acceptTcpHandler函数,具体实现为sys/socket.h/accept函数的包装。服务器初始化时,会将这个处理器与套接字的AE_READABLE事件关联起来。

  • 命令请求处理器:接受来自客户端的命令请求。

    networking.c/readQueryFromClient函数,具体实现为unistd.h/read函数的包装。

  • 命令回复处理器:向客户端返回命令的执行结果。

    networking.c/sendReplyToClient函数,具体实现为unistd.h/write函数的包装。

  • 复制处理器:主从服务器的复制操作。

2 时间事件

原理

Redis的时间事件分为两类:

  • 定时事件:在指定一段时间后执行一次。
  • 周期性事件:每隔一段时间就执行一次。

时间事件主要有三个属性:

  • id
  • when:毫秒进度的UNIX时间戳,事件的到达时间。
  • timeProc:时间事件处理器,事件到达时,负责处理事件。

一个事件是定时事件还是周期性事件,取决于时间事件处理器的返回值:

  • 返回ae.h/AE_NOMORE就是定时事件,到达一次后就删除
  • 返回非AE_NOMORE的整数值就是周期性事件,事件到达后,根据返回值对when属性进行更新。

实现

服务器的所有时间事件存放在一个无序链表(不按when属性排序)中,每当时间事件处理器运行时,遍历整个链表,找到已到达的事件,调用相应的事件处理器。

API

函数 参数 作用
ae.c/aeCreateTimeEvent 毫秒数,时间事件处理器 将新的时间事件添加到服务器
ae.c/aeDeleteFileEvent 事件ID 删除时间事件
ae.c/aeSearchNearestTimer 返回到达时间最近的事件
ae.c/processTimeEvents 时间事件的执行器,遍历并调用事件

serverCron函数

serverCron函数的工作包括:

  • 更新服务器的统计信息,如时间、内存占用、数据库占用
  • 清理过期的键值对
  • 关闭和清理失效的连接
  • 尝试AOF或RDB持久化
  • 如果是主服务器,对从服务器定期同步
  • 如果是集群模式,对集群进行同步和测试连接

3 事件的调度与执行

调度和执行由ae.c/aeProcessEvents函数负责。

def aeProcessEvents():
    # 获取最近的事件
    time_event = aeSearchNearestTimer()
    
    # 计算最近的事件还有多少毫秒
    remaind_ms = time_event.when - unix_ts_now()
    
    # 如果事件已到达
    if remaind_ms < 0:
        remaind_ms = 0
        
    # 根据remaind_ms的值,创建timeval结构
    timeval = create_timeval_with_ms(remaind_ms)
    
    # 阻塞并等待文件事件
    # 如果remaind_ms为0,那么aeApiPoll调用之后马上返回,不阻塞
    aeApiPoll(timeval)
    
    # 处理所有已产生的文件事件
    processFileEvents()
    
    # 处理所有已到达的时间事件
    processTimeEvents()
    

调度和执行的规则如下:

  • aeApiPoll函数的最大阻塞时间由到达时间最接近当前时间的事件决定,避免服务器的频繁轮询。
  • 如果处理完一次文件事件后,未有时间事件到达,则再次处理文件事件。
  • 对事件的处理都是同步、有序、原子地执行。不会中断、抢占事件处理。
  • 时间事件的处理时间,通常比其设定的到达时间晚一些。

将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下:

def main():
    # 初始化服务器
    init_server()
    # 一直处理事件,直到服务器关闭为止
    while server_is_not_shutdown():
        aeProcessEvents()
    # 服务器关闭,执行清理操作
    clean_server()

从事件处理的角度来看,服务器运行流程如下: