Redis 6.0 的多线程,并非指命令处理逻辑的多线程(命令执行仍然是单线程的),而是特指网络 I/O 的多线程,其核心目标是优化大量网络 I/O 带来的性能瓶颈,提升吞吐量,尤其是在高并发场景下。
Redis 6.0 之前 - 单线程模型与IO多路复用
在 6.0 之前,Redis 是著名的单线程模型(主要指的是处理用户请求的核心模块)。它之所以能高效处理海量并发连接,全靠其核心机制:IO 多路复用。
IO多路复用 (I/O Multiplexing):
简单来说,它允许一个线程监控多个网络连接(Socket),当这些连接中有数据可读(客户端发来请求)或可写(可以发送响应)时,它会通知工作线程。在 Redis 中,最常用的是 epoll
(Linux) 这样的系统调用。
单线程下的请求处理流程(经典模型):
-
监听与接收 (Listen & Accept):
- 主线程通过
epoll
监听所有客户端连接(Listening Socket)上的ACCEPT
事件。 - 当有新连接到来时,主线程调用
accept()
系统调用,建立连接,并将新生成的客户端 Socket 也注册到epoll
实例中,监听其上的READ
事件。
- 主线程通过
-
读取请求 (Read Request):
- 主线程通过
epoll
监听所有已建立连接的客户端 Socket 上的READ
事件。 - 当某个客户端发送了数据(一个命令请求),该 Socket 变为可读状态,
epoll
会通知主线程。 - 主线程会亲自、同步地 从内核缓冲区中将客户端的请求数据读取到用户空间,并解析命令。这个过程是 同步阻塞 的,虽然时间极短,但在海量连接同时发来请求时,它会成为瓶颈。
- 主线程通过
-
执行命令 (Execute Command):
- 主线程根据解析出的命令,操作内存中的数据(如
GET
,SET
,LPUSH
等)。 - 这一步始终是单线程的! 这是 Redis 保证原子操作和简单性的基石,避免了昂贵的锁开销。
- 主线程根据解析出的命令,操作内存中的数据(如
-
写入响应 (Write Response):
- 命令执行完毕后,会产生响应数据(如
"OK"
或具体的 value)。 - 主线程将响应数据写入到该客户端 Socket 的内核发送缓冲区中。
- 如果发送缓冲区已满,主线程会监听该 Socket 的
WRITE
事件,直到可写后再写入。这个写入过程也是 同步 的。
- 命令执行完毕后,会产生响应数据(如
单线程模型的瓶颈:
在整个流程中,步骤 2(读请求)和步骤 4(写响应) 本质上是 I/O 操作,尤其是读取大量数据或向大量客户端发送响应时,它们会消耗可观的 CPU 时间。虽然每个操作的延迟是微秒级的,但在数十万并发连接下,这些微秒累积起来就非常可观了。主线程忙于这些 I/O 杂活,就会导致执行命令(核心业务)被延迟,整体吞吐量受限。
Redis 6.0 的多线程优化
Redis 6.0 引入了多线程来处理上述的 I/O 瓶颈。它增加了一种新的机制:I/O Threads。
核心思想:
让主线程不再亲自执行所有耗时的读取请求和写入响应的操作,而是将这些操作分摊给多个 I/O 线程并行处理。主线程只负责最核心的命令执行。
关键特点:
- 默认关闭:配置项
io-threads 4
(表示使用 4 个 I/O 线程,通常 4-6 个就足够了,超过收益不大)。 - 只处理 I/O:I/O 线程只负责读取客户端请求和回包(响应),绝不负责命令解析和执行。
- 主线程协调:主线程仍然等待
epoll
事件,但它现在是“管理者”,负责分配任务和执行命令。
多线程模式下的请求处理流程:
结合下图,可以更直观地理解整个流程:
详细步骤解释:
-
读取阶段 (Read Phase):
- 主线程通过
epoll_wait
捕获到一批有读事件(有请求到达)的 Socket。 - 主线程不再自己读取,而是将这些 Socket分配到一个等待读取的队列中。
- 主线程唤醒正在休眠的 I/O 线程(如果有的话)。
- I/O 线程并行工作,各自从队列中取出 Socket,将请求数据从内核缓冲区读取到用户空间(即读到每个连接对应的 Client Buffer 中)。
- 主线程等待所有 I/O 线程完成读取操作(自旋等待)。
- 注意:命令的解析(Parsing)是由主线程在之后完成的。
- 主线程通过
-
执行阶段 (Execution Phase):
- 这是最关键的一步,并且仍然是单线程的!
- 所有客户端的请求数据都已就绪,主线程按顺序、串行地遍历所有客户端连接,解析其缓冲区的命令,执行命令,并将响应结果写入到各自的客户端响应缓冲区中。
- 这一步保证了所有命令的原子性,不存在竞态条件。
-
写入阶段 (Write Phase):
- 主线程执行完所有命令后,手中持有了一批需要发送响应的 Socket。
- 主线程将这些 Socket分配到一个等待写入的队列中。
- 主线程再次唤醒 I/O 线程。
- I/O 线程并行工作,各自从队列中取出 Socket,将响应数据从用户空间的响应缓冲区写入到内核的网络发送缓冲区中。
- 主线程等待所有 I/O 线程完成写入操作。
- 之后,主线程清空一些状态,重新回到
epoll_wait
循环,处理下一批事件。
总结与对比
特性 | Redis 6.0 之前 (纯单线程) | Redis 6.0+ (多线程I/O) |
---|---|---|
核心模型 | 单线程处理所有事情:网络I/O、命令执行 | 多线程I/O,单线程命令执行 |
优势 | 无锁,绝对原子性,模型简单 | 高并发下吞吐量显著提升,尤其利于大键或管道批处理 |
瓶颈 | 网络I/O(读请求/写响应)成为主要瓶颈 | 命令执行本身(CPU密集型操作)可能成为新瓶颈 |
适用场景 | 并发连接数不是极端高,或命令本身开销大的场景 | 需要应对极高网络吞吐量的场景 |
简单比喻:
- 单线程Redis:像一个全能服务员,从迎客、点菜、炒菜、上菜、收银全是一个人干。人一多就忙不过来。
- 多线程I/O的Redis:像一家餐厅。主线程是唯一的大厨(炒菜),I/O线程是一群服务员。服务员(I/O线程)负责迎接客人、记录点单(读请求)和上菜(写响应)。而核心的炒菜工作(执行命令)永远由大厨一个人完成,保证味道一致(原子性)。这样效率就高多了。
因此,Redis 6.0 的多线程是在保持其核心数据操作原子性和简单性的前提下,对性能瓶颈的一个精准打击,极大地提升了其在现代高性能网络环境下的竞争力。