Redis线程模型

Redis 的线程模型其实是一个很有意思的设计。表面看起来很“反直觉”:单线程 + 超高并发。很多人第一反应是——单线程怎么可能快?但当你拆开看,会发现这是一个非常工程化、极简主义的设计。


一、Redis 的线程模型是什么

经典版本(直到 Redis 5 之前)可以总结成一句话:

Redis 使用单线程处理命令,但通过 I/O 多路复用处理高并发连接。

也就是说:

模块 是否多线程
网络 I/O I/O 多路复用
命令执行 单线程
数据结构操作 单线程

所以 Redis 的核心循环可以抽象成:

1
2
3
4
5
6
7
8
事件循环 (Event Loop)

while(true) {
监听socket事件
读取客户端请求
执行命令
返回结果
}

这个循环也叫:

Reactor 事件驱动模型

Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。

  • 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
    虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。

二、Redis 的核心结构:事件循环

Redis 内部有一个非常核心的组件:

aeEventLoop(事件循环)

整个流程是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
客户端请求

socket

I/O 多路复用监听
(select / poll / epoll / kqueue)

事件循环

读取命令

执行命令(单线程)

返回结果

流程图可以想象成:

1
2
3
4
5
6
7
8
9
10
11
12
客户端1 ─┐
客户端2 ─┤
客户端3 ─┤
客户端4 ─┘

I/O多路复用

事件队列

单线程执行

返回结果

这里的关键就是:

I/O 多路复用


三、I/O 多路复用是什么

Redis 并不是一个连接一个线程。

它是:

一个线程监听成千上万个连接

底层依赖操作系统提供的机制:

系统 技术
Linux epoll
Mac kqueue
Windows select

这些机制允许:

1
2
3
一个线程
同时监听
上万个 socket

当某个 socket 有数据时,操作系统会通知 Redis。

Redis 再去处理这个请求。

所以线程并不会:

1
等待某个客户端

而是:

1
谁有数据 → 处理谁

这就是 事件驱动模型


四、Redis 为什么用单线程

原因其实很现实:

1 避免锁

多线程最大的问题是:

1

例如:

1
2
线程A修改Hash
线程B修改Hash

必须加锁:

1
synchronized / mutex

否则数据会错。

而 Redis 的数据结构:

1
2
3
4
5
String
List
Hash
Set
ZSet

全部在 内存中操作

如果多线程:

锁竞争会严重影响性能。

Redis 直接选择:

不用锁 → 单线程

设计上非常干净。


2 CPU 不是瓶颈

Redis 的瓶颈通常是:

1
网络 I/O

而不是:

1
CPU

大部分命令执行非常快,比如:

1
2
3
4
GET
SET
HGET
LPUSH

时间复杂度:

1
O(1)

几十纳秒级。

所以:

1
CPU远远没跑满

真正慢的是:

1
网络收发

因此单线程完全够用。


3 内存操作极快

Redis 的数据全部在:

1
RAM

而不是磁盘。

内存访问速度:

1
纳秒级

磁盘访问:

1
毫秒级

差了 10^6 倍

所以 Redis 执行命令非常快。


五、Redis 6 的线程模型变化

从 **Redis 6 开始,引入了:

多线程 I/O

但注意一个关键点:

1
命令执行仍然是单线程

只把 网络读写 改成多线程。

结构变成:

1
2
3
4
5
6
7
8
主线程
├─ 执行命令
├─ 事件循环
└─ 调度 I/O 线程

I/O线程1
I/O线程2
I/O线程3

流程:

1
2
3
4
5
6
7
客户端请求

I/O线程读取socket

主线程执行命令

I/O线程返回结果

这样可以提高:

1
网络吞吐量

但仍然保持:

1
数据操作单线程

避免锁。

Redis6 之前采用单线程模型,网络 I/O 和命令执行都由主线程完成,通过 I/O 多路复用(如 epoll)同时监听多个客户端连接。

Redis6 之后引入了多线程 I/O,但只是将 socket 的读写操作交给多个 I/O 线程处理,而 命令执行仍然由主线程完成,这样既能提高网络吞吐量,又避免了多线程操作数据结构带来的锁竞争


六、Redis 线程模型总结(面试版)

如果是面试回答,可以这样说:

Redis 采用 单线程事件驱动模型
通过 I/O 多路复用(epoll/kqueue/select) 同时监听多个客户端连接。
当 socket 可读时,Redis 在 事件循环 中读取请求,然后 单线程执行命令并返回结果

单线程设计的优点是:

  • 避免多线程锁竞争
  • 内存操作本身非常快
  • Redis 命令大多是 O(1)

从 Redis 6 开始,引入了 多线程 I/O,但 命令执行仍然由主线程完成


七、很多人容易误解的一件事

很多人说:

Redis 是单线程的。

严格来说,这是 不完全正确 的。

Redis 其实还有后台线程,例如:

  • AOF rewrite
  • RDB 持久化
  • lazy free
  • bio thread

所以更准确的说法是:

Redis 的命令执行是单线程的。


八、既然 Redis6 有多线程,为什么命令执行还不用多线程?

核心原因是三个字:

1
锁开销

如果命令执行多线程:

1
2
3
线程A   SET key
线程B DEL key
线程C HSET key

就必须:

1
加锁

锁会带来:

  • 上下文切换
  • CPU cache 失效
  • lock contention

在内存数据库里,这些开销可能 比命令执行本身还大

所以 Redis 的哲学非常极端:

宁可单线程,也不要锁。