Skip to content

Redis的线程模型介绍

经典单线程模型(Redis 6.0 之前)

在版本 6.0 之前,Redis 的核心网络模型是单线程的。这意味着处理客户端网络请求、解析命令、执行命令和写回响应这整个过程,都由一个主线程来完成。

工作原理:I/O 多路复用

这个单线程模型的核心是基于 I/O 多路复用技术(如 epoll, kqueue, select)。其工作流程可以概括为一个单 Reactor 模式: * 事件循环(Event Loop):主线程在一个事件循环中不断地等待事件发生。 * 事件分发器:利用 I/O 多路复用 API (如 epoll_wait) 来监听多个客户端套接字(socket)。当某个套接字准备好读或写时,API 就会返回。 * 事件处理器:主线程根据事件类型(新连接、读就绪、写就绪),调用相应的处理器函数来执行操作。

为什么选择单线程?

Redis 的作者之所以选择单线程模型,主要基于以下几点考虑: * CPU 通常不是瓶颈:对于 Redis 这种内存数据库来说,绝大多数操作是内存中的数据读写,速度极快。性能瓶颈通常在于网络延迟或内存大小,而非 CPU 计算。 * 避免多线程开销: * 上下文切换:多线程需要频繁地在 CPU 核心之间进行上下文切换,这会带来性能损耗。 * 锁机制开销:如果采用多线程,为了保证数据的一致性,必须引入锁等同步机制。这不仅会增加代码的复杂度,还会在高并发场景下因锁竞争而降低性能。 * 实现简单,易于维护:单线程模型使得代码逻辑清晰,避免了处理复杂的并发问题,更容易开发和维护。

后台线程的引入 (Redis 4.0)

严格来说,Redis 早在 4.0 版本就已经不是纯粹的单线程程序了。为了解决一些耗时操作阻塞主线程的问题,Redis 引入了后台线程来处理异步任务。

最典型的例子就是删除大键(big key)的操作。像 DEL 命令是同步阻塞的,如果删除一个包含数百万个元素的 Hash 或 Set,主线程会被阻塞数秒,导致所有其他客户端请求被延迟。

为了解决这个问题,Redis 4.0 引入了异步非阻塞命令: * UNLINKDEL 命令的异步版本。当执行 UNLINK 时,主线程仅将键从键空间(keyspace)中移除,这个操作是瞬时的。实际的内存回收工作则被交由一个后台线程异步完成。 * FLUSHALL ASYNC / FLUSHDB ASYNC:异步清空数据库。

这些后台线程的引入,标志着 Redis 开始利用多线程来优化特定场景,但其核心的网络请求处理部分依然是单线程的。

I/O 多线程网络模型 (Redis 6.0)

随着硬件发展,网络接口的速度越来越快,Redis 的网络 I/O 处理逐渐成为新的性能瓶颈。单个主线程在读取和解析大量客户端请求,以及将大量响应数据写回客户端时,会耗费大量的 CPU 时间,限制了整体的 QPS。

因此,Redis 在 6.0 版本中正式引入了 I/O 多线程(I/O Threading),将网络数据的读写任务从主线程中剥离出来。

工作原理与设计

Redis 6.0 的多线程模型并非标准的 Master-Worker 模式(如 Nginx),它有自己独特的设计:

  1. 线程分工明确

    • 主线程(Main Thread):仍然负责接收新连接、解析和执行命令、管理数据等核心工作。
    • I/O 线程(I/O Threads):只负责读取网络请求和将响应数据写回网络。它们不执行任何命令
  2. 核心流程

    • 接收连接:主线程的事件循环接收新的客户端连接,并创建 client 对象。
    • 读取任务分发:当客户端发送命令时,主线程不再立即读取和解析。而是将待读取的 client 放入一个全局队列中。然后,主线程通过轮询(Round-Robin)策略,将这些 client 分配给各个 I/O 线程和主线程自己。
    • I/O 线程读取和解析:I/O 线程从自己的任务队列中取出 client,负责从 socket 读取数据,并解析出第一条命令,但不执行
    • 主线程执行命令:主线程会忙轮询等待所有 I/O 线程完成读取任务。之后,主线程会遍历所有已完成读取的 client,依次执行它们的命令。
    • 写入任务分发:命令执行完毕后,响应数据被放入 client 的写出缓冲区。主线程将需要写回响应的 client 放入另一个全局队列,并再次通过轮询策略分配给所有 I/O 线程和主线程自己。
    • I/O 线程写回响应:I/O 线程从各自的任务队列中获取 client,并将它们的写出缓冲区数据通过 socket 发送给客户端。
    • 主线程善后:主线程同样会忙轮询等待所有 I/O 线程完成写入任务,并处理后续工作(如为未写完的 client 注册处理器)。