Redis 是单线程还是多线程?
Redis 是单线程还是多线程?
此题的核心结论是:Redis 在处理客户端命令的核心部分,仍然是单线程的。但在其他后台任务和网络 I/O 方面,则是多线程去处理的。
我们可以从两个阶段来看这个问题:Redis 6.0 之前 和 Redis 6.0 之后。
一、Redis 6.0 之前:经典的单线程模型
在这个时期,我们可以说 Redis 是单线程的。这里的 “单线程” 指的是处理客户端命令请求、执行命令、返回数据这个核心流程,是由一个主线程来完成的。
为什么这么设计?这背后是精妙的权衡:
- 避免锁的竞争和上下文切换带来的性能损耗: 这是最核心的原因。多线程虽然能利用多核,但会引入复杂的锁机制来保证数据一致性。频繁的加锁、解锁和线程上下文切换本身就会消耗大量 CPU 资源。Redis 的操作基本都是内存操作,速度极快,在这种情况下,单线程模型的性能可能反而高于多线程模型。
- 保证原子性,简化开发模型: 由于所有命令都是按顺序执行的,每个命令在执行过程中都不会被其他命令打断。这天然地保证了单个命令的原子性,使得 Redis 的内部实现变得非常简单和稳定。
- 高效的数据结构: Redis 的瓶颈往往不在于 CPU,而在于内存访问和网络 I/O。它使用了高效的数据结构(如哈希表、跳表等),即使单线程也能提供极高的吞吐量。
但是,这并不意味着 Redis 进程里只有一个线程。 在后台,Redis 会 fork 出一些子线程或后台线程来处理特定任务,例如:
- 持久化(RDB):
bgsave命令会fork一个子进程来将数据快照写入磁盘,避免阻塞主线程。 - 异步删除(unlink): 对于非常大的键,直接在主线程删除可能会阻塞太久。Redis 提供了
UNLINK命令,将其标记为删除,然后由后台线程异步回收内存。 - AOF 重写: 同样会
fork子进程来完成。
所以,在 6.0 之前,我们可以这样总结:命令处理是单线程的,但某些后台任务是多线程/多进程的。
二、Redis 6.0 之后:迈向多线程的 I/O
随着网络硬件的飞速发展,千兆、万兆网卡越来越普遍。此时,网络 I/O 的处理速度可能成为新的瓶颈。在单线程模型下,主线程需要独自完成读取请求、解析、执行命令、返回数据这一整套流程。当客户端连接数非常多时,这个单线程可能忙于处理网络 I/O,而无法充分发挥 CPU 的计算能力。
为了解决这个问题,Redis 6.0 引入了多线程 I/O。
这个多线程指的是:将网络数据的读写这类耗时操作,交给多个 I/O 线程并行处理,而命令的执行本身,依然由那个单一线程(主线程)来完成。
它的工作流程可以这样理解:
- I/O 线程并行读: 主线程负责监听和分配连接。当有数据到来时,主线程将 Socket 分配给多个 I/O 线程,由它们并行地读取请求数据并进行解析。
- 主线程串行执行: 解析好的命令被放入一个队列。主线程会按顺序从队列中取出命令并执行(这是关键,命令执行仍然是单线程的)。
- I/O 线程并行写: 命令执行完毕后,主线程将结果数据放入另一个队列。多个 I/O 线程会并行地从队列中取出结果,并通过网络发送给各个客户端。
我们可以修改 Redis 的 redis.conf 配置文件,来打开此功能,如下图所示:
Redis 多线程 I/O 配置
引入多线程 I/O 的好处是显而易见的: 它将最耗时的网络读写任务分摊了出去,解放了主线程,使得主线程可以更专注于、更快速地执行命令。从而,在高并发场景下,Redis 的整体吞吐量可以得到大幅提升(官方数据是可以翻倍)。
