Skip to content

Redis事件机制

核心思想:单线程为何如此高效?—— 事件驱动模型

Redis的惊人性能,尤其是在6.0版本之前的单线程模型下,主要归功于其高效的事件驱动机制。Redis的网络IO和命令读写由单个主线程处理,但这并不意味着它一次只能处理一个客户端。其核心是利用了I/O多路复用技术,使得单个线程能并发处理成千上万个网络连接,避免了在等待IO时造成的CPU资源浪费。

Redis自己实现了一个简洁的事件库ae_event,它主要处理两类事件:

  1. 文件事件 (File Event):处理网络IO,是性能的关键。
  2. 时间事件 (Time Event):处理定时任务,如周期性的serverCron

整个机制的核心是事件循环 (Event Loop),由aeEventLoop对象管理,它不断地循环处理已就绪的文件事件和已到期的时间事件。


一、 文件事件 (File Event) - 网络IO的核心

文件事件是Redis对套接字(Socket)操作的抽象。当一个套接字可读、可写或出现错误时,就会产生一个文件事件。

1. Reactor设计模式

Redis的文件事件处理器是基于Reactor模式实现的,其主要组件包括:

  • 套接字 (Sockets):包括监听套接字和客户端连接套接字。
  • I/O多路复用程序:封装了epollkqueueselect等系统调用。它能同时监听多个套接字,并将产生事件的套接字放入一个就绪队列。这是实现并发的关键
  • 文件事件分派器 (Dispatcher):从就绪队列中取出套接字,根据事件类型(读/写)调用对应的事件处理器。
  • 事件处理器 (Handlers):具体的业务逻辑代码,如接受新连接(accept)、读取命令(read)、发送响应(write)等。

2. IO多路复用模型(以epoll为例)

这个模型可以类比于医院的分诊台

  • Redis主线程:就像一位专家医生,负责核心的诊断和治疗(处理命令)。
  • Linux内核的epoll:就像分诊台。它负责处理所有“杂事”,如监听所有病人(客户端连接)的到来、测温、登记(监听连接请求和数据请求)。
  • 事件队列:就像分诊台前的排队队列
  • 回调机制:分诊台处理完一个病人后,会通知医生,并把病历交给医生。同样,epoll监听到事件后,会将事件放入队列,并触发Redis注册的回调函数。

工作流程: 1. Redis主线程将所有需要监听的套接字(FDs)通过epoll_ctl注册到内核的epoll实例中。 2. 主线程调用epoll_wait阻塞在这里等待事件发生。此时主线程不消耗CPU。 3. 当某个或某些套接字上有事件发生时(如新连接、数据到达),epoll_wait被唤醒并返回就绪的事件列表。 4. 主线程遍历就绪事件列表,根据事件类型调用预先注册好的回调函数进行处理。 5. 处理完所有就绪事件后,再次循环调用epoll_wait,等待下一批事件。

这样,Redis主线程永远在处理“已就绪”的事件,从不空等,从而实现了单线程处理高并发IO。


二、 时间事件 (Time Event) - 定时任务

时间事件用于处理定时或周期性的任务。

  • 分类
    • 定时事件:在指定时间后执行一次,然后删除。
    • 周期性事件:每隔指定时间就执行一次。通过事件处理器的返回值来决定是否重复(返回AE_NOMORE则为定时事件)。
  • 实现
    • 所有时间事件被存放在一个无序链表中。
    • 每次事件循环时,程序会遍历此链表,检查是否有事件已到期,并执行其处理器。
    • 由于Redis中的时间事件非常少(正常模式下只有serverCron),所以遍历链表的性能开销可以忽略不计。

三、 aeEventLoop 的具体实现流程

  1. 创建事件管理器 (aeCreateEventLoop)

    • 在服务器初始化时调用,创建一个aeEventLoop对象。
    • 初始化文件事件表events数组,按fd索引)和就绪事件表fired数组)。
    • 初始化时间事件链表timeEventHead指针)。
    • 调用aeApiCreate,在底层创建epoll实例(epoll_create),并将其与事件循环关联。
  2. 创建/注册文件事件 (aeCreateFileEvent)

    • 当需要监听某个套接字fd的读/写事件时调用此函数。
    • 函数内部调用aeApiAddEvent,最终通过epoll_ctl系统调用将fd和关心的事件(EPOLLIN/EPOLLOUT)注册到内核的epoll实例中。
    • 同时,将事件的回调函数(Handler)和私有数据(clientData)保存在events数组中对应的fd位置。
  3. 事件处理循环 (aeProcessEvents) 这是事件循环的核心,在一个while循环中被反复调用。

    1. 计算超时时间:遍历时间事件链表,找出最快到期的那个事件,计算出距离现在还有多久。这个时间将作为epoll_wait的超时参数。
    2. 等待IO事件:调用aeApiPoll(内部调用epoll_wait),阻塞等待文件事件的发生,最大阻塞时间就是上一步计算出的超时时间。
    3. 处理文件事件
      • epoll_wait返回后,aeApiPoll会将所有就绪的事件(fd和事件类型)填充到fired就绪事件表中。
      • 事件分派器遍历fired表,根据fd在events表中找到对应的回调函数,并执行它。
    4. 处理时间事件
      • 调用processTimeEvents函数,再次遍历时间事件链表。
      • 执行所有已经到期的时间事件处理器。
      • 根据处理器返回值,更新周期性事件的下一次执行时间,或标记一次性事件为待删除。
  4. 删除事件 (aeDeleteEventLoop)

    • 当不再需要监听某个事件时,调用此函数。
    • 它会通过epoll_ctl从内核的epoll实例中移除对该事件的监听。

总结表

组件/概念 核心作用 实现细节
aeEventLoop 事件循环核心,调度文件事件和时间事件。 包含文件事件表、就绪事件表、时间事件链表。
文件事件 处理网络IO,实现高并发。 基于Reactor模式,对socket操作的抽象。
时间事件 处理定时/周期性任务。 无序链表存储,通过遍历检查是否到期。
I/O多路复用 并发基础,允许单线程同时监听多个IO流。 封装epoll等系统调用,由aeApiPoll中的epoll_wait实现阻塞等待。
文件事件分派器 任务分发,根据就绪事件类型调用相应处理器。 遍历fired就绪事件表,并执行events表中注册的回调函数。
aeProcessEvents 单次循环的主体,驱动整个事件处理流程。 顺序执行:计算超时 -> 等待IO -> 处理文件事件 -> 处理时间事件。