Redis 线程模型

主线程和后台线程

由主线程完成:接收请求->解析请求 ->数据操作->发送数据

主线程和后台线程采用任务队列交互,后台线程包括:

  1. 关闭文件(BTO_CLISE_FILE 队列)
  2. AOF 持久化(BIO_AOF_FSYNC 队列)
  3. 异步释放内存(BIO_LASY_FREE 队列): lazyfree 线程。unlink flushdb flushall 会把删除操作异步执行。

初始化过程

  1. 创建一个 epoll 对象和一个服务端 socket 对象, 然后绑定端口和监听该 socket;
  2. 将调用 epoll_ctl() 将 listen socket (文件描述符)加入到 epoll,同时注册「连接事件」处理函数

Note

TCP/IP 网络编程中,每次客户端与服务器建立连接,都会在服务器端创建一个新的 客户端 socket(称为 已连接 socketconnected socket)。这与服务器端的 监听 socket(listen socket) 完全不同。

事件循环

Reids 处理请求过程:初始化完成后,主线程进入事件循环函数:

  1. 先看发送缓存区是否有数据,有则发送

  2. 等待事件到来:

    1. 连接事件:获取 connected socket -> 将已连接的 socket 加入到 epoll -> 注册读事件(指的是 epoll 监听到 socket 内的数据可读)处理函数;

      不注册写事件处理函数,通常默认情况下,socket 总是可写(除非发送缓冲区已满)。注册写事件会导致频繁触发,造成资源浪费。只有当发送缓冲区的数据发送一半无法发送了,说明此时 socket 进入了不可写状态,才需要注册写事件处理函数

    2. 读事件:调用读事件处理函数:调用 read 获取客户端发送的数据 -> 解析命令 -> 处理命令 -> 将客户端对象添加到发送队列 -> 将执行结果写到发送缓存区等待发送;

    3. 写事件:调用写事件处理函数:通过 write 函数将客户端发送缓存区里的数据发送出去,如果这一轮数据没有发送完,就会继续注册写事件处理函数,等待 epoll_wait 发现可写后再处理 。

Note

Epoll

epoll 是 Linux 内核中高效的 I/O 多路复用机制。它的高性能主要源于 事件驱动(Event-Driven)高效的数据结构(红黑树 + 双向链表)

每个文件描述符(fd)在被注册时会被存储在一个 红黑树(RB Tree) 中,只有状态发生变化的文件描述符会被添加到双向链表 Ready List。事件触发(epoll_wait)只需遍历 Ready List,而非所有文件描述符。

epoll 实现非阻塞(Non-Blocking I/O)的核心在于结合 非阻塞文件描述符事件驱动机制。它通过以下三种机制确保非阻塞 I/O:

  • 非阻塞文件描述符(在文件描述符上设置 O_NONBLOCK 标志):在非阻塞模式下,当尝试读取或写入文件描述符时,如果没有数据可读或可写,read()write() 函数会立即返回 -1,并设置 errnoEAGAIN(表示资源暂时不可用),而不会阻塞进程。
  • 事件驱动,只有在事件触发时才会通知。
  • 如果没有事件准备好,epoll_wait 会挂起,节省 CPU 资源(而不是空轮询)。

触发模式:

水平触发(Level Triggered,LT,Redis 采用)

  • 只要文件描述符的状态保持 就绪(如可读),事件会持续触发。
  • 确保数据不会丢失,即使数据没有一次性读取完。

边缘触发(Edge Triggered,ET)

  • 只有在状态 变化(从不可用到可用) 时才触发事件。
  • 一次性通知,不会重复触发。

多路复用 I/O:

  1. 一个线程或进程同时监控多个文件描述符(fd)。

  2. 当文件描述符状态(如可读、可写)发生变化时,立即通知并处理。

  3. 避免每个 I/O 任务都创建独立线程,节省资源。

为什么快

  1. 内存完成
  2. 单线程模型避免竞争和死锁,而CPU 并不是制约 Redis 性能表现的瓶颈所在,更多情况下是受到内存大小和网络I/O的限制。
  3. I/O 多路复用

Redis 6.0+

单线程局限于数据操作部分,而网络 I/O 引入了多线程模型。默认情况下 I/O 多线程只针对发送响应数据(write client socket),并不会以多线程的方式处理读请求(read client socket)。要想开启多线程处理客户端读请求,就需要把 Redis.conf 配置文件中的 io-threads-do-reads 配置项设为 yes。

至此,Redis 启动的线程包括:

  1. 主线程
  2. AOF
  3. 关闭文件
  4. 异步释放内存
  5. 多个网络 I/O 线程