问题: Redis 是单线程的吗?请从网络模型、内存操作、数据结构设计等角度全面剖析其高性能的底层原因。

👇 点击展开/折叠 面试背诵模板(建议先背这个) Redis 并非绝对的单线程,需分版本和维度来看:命令执行(核心业务逻辑)始终是单线程,但 6.0 之前已有后台线程处理持久化、过期清理等任务,6.0 之后引入了多 I/O 线程处理网络读写。高性能的底层原因有五点:① 纯内存操作,读写速度比磁盘快几个数量级;② 高效的数据结构(SDS、跳表、压缩列表等),针对不同场景优化存储与访问;③ 单线程命令执行,避免了多线程的上下文切换、锁竞争和死锁开销;④ I/O 多路复用(epoll/kqueue),单线程通过一个事件循环监控大量 socket,仅处理就绪事件,实现高并发网络通信;⑤ 非阻塞 I/O,所有 socket 设为非阻塞模式,配合多路复用避免线程阻塞。Redis 官方指出,CPU 通常不是性能瓶颈,瓶颈在于内存或网络带宽。
---

单线程 vs 多线程:到底怎么理解?

狭义(命令执行层面):Redis 处理客户端请求的获取(socket 读)、解析、执行、返回(socket 写)等核心流程,都由一个主线程顺序串行完成。

广义(整个 Redis 进程):Redis 内部一直有其他线程在运行,包括清理过期键值对、释放无用连接、内存淘汰策略执行、BGSAVE 生成 RDB 快照、AOF rewrite 等。所以 Redis 6.0 之前本质上是“单线程处理命令 + 多线程处理后台任务”的混合模型。

版本演进:

  • v4.0:引入多线程处理耗时任务,如异步删除命令 unlink
  • v6.0:核心网络模型引入多 I/O 线程,将 socket 读写操作分配给多个线程并行处理,但命令执行仍由主线程完成

高性能的五大底层原因

① 纯内存操作

Redis 是基于内存的键值存储,每次读写操作本质上就是内存变量的读写。内存访问速度比磁盘快几个数量级,这是 Redis 快的基础。

② 高效的数据结构

Redis 针对不同数据类型设计了专属的底层结构:

  • 动态字符串(SDS):替代 C 字符串,预分配内存减少重分配开销
  • 跳跃表(Skip List):实现有序集合(ZSet),查询和插入均为 O(log N)
  • 压缩列表(ZipList):连续内存存储,避免链表指针的内存消耗
  • 字典(Dict):哈希表实现,O(1) 的读写性能

③ 单线程命令执行(避免锁与上下文切换)

单线程的优势在于:

  • 无需考虑锁竞争、加锁解锁操作
  • 不存在死锁风险
  • 减少线程创建/销毁和上下文切换的 CPU 消耗
  • 能够安全使用各种“线程不安全”的命令(如 LPUSH)

④ I/O 多路复用(核心网络模型)

Redis 基于 Reactor 模式开发了文件事件处理器(file event handler),这是单线程的,但通过 I/O 多路复用机制同时监听多个 socket。

工作流程:

  1. I/O 多路复用程序(Linux 下为 epoll)监听多个 socket
  2. 将产生事件的 socket 放入内存队列
  3. 事件分派器从队列中取出 socket,根据事件类型交给对应的事件处理器

这种设计使得单线程可以处理数万并发连接,因为线程不会被阻塞在某个慢速连接上。

⑤ 非阻塞 I/O

Redis 将所有客户端 socket 设为非阻塞模式。在非阻塞 I/O 下,read() 调用如果数据未就绪会立即返回 EAGAIN,而不是阻塞线程。配合 I/O 多路复用,只有在确定 socket 可读时才进行读取,最大化 CPU 利用率。

为什么 Redis 官方选择单线程(6.0 之前)?

官方给出的核心原因:

  • Redis 的性能瓶颈主要在于内存和网络 I/O,CPU 不是瓶颈
  • 普通 Linux 系统上 Redis 每秒可处理 100 万个请求
  • 单线程模型代码可维护性高,实现简单
  • 多线程会引入并发读写问题、线程切换、加锁解锁等复杂性和性能损耗

6.0 为何引入多 I/O 线程?

尽管单线程模型已经非常高效,但随着硬件发展,其局限性逐渐显现:

  • CPU 利用率有限:现代服务器多为多核,单线程无法充分利用
  • 网络 I/O 瓶颈:socket 的 read/write 系统调用占用了大部分 CPU 时间,在高并发下成为性能瓶颈

6.0 的多 I/O 线程模型核心设计:

  • I/O 线程仅负责网络读写和协议解析
  • 命令执行仍由主线程完成(保持单线程的无锁优势)
  • 主线程将请求分发给 I/O 线程并行读取,读取完成后主线程串行执行命令,最后再由 I/O 线程并行回写响应

这种“并行 I/O + 串行命令”的设计,既提升了网络吞吐量,又保留了单线程命令执行的简单性和一致性。