一、 网络 I/O 与 CPU 开销分析

理解 Redis 6.0 多线程架构的前提,是明确计算机硬件与操作系统在处理网络请求时的底层运行机制。当客户端向 Redis 服务器发送一条简单的数据读取命令时,服务器端的处理流程严格按照物理与逻辑的层级分为三个主要阶段:网络数据读取内存指令执行以及网络数据回写。在这三个阶段中,资源消耗的分布存在极大的差异。

剥离所有复杂的业务逻辑,我们假设客户端向 Redis 服务器发送了一条最基础的读取命令:

get name

阶段一:网络数据读取

客户端发送的 get name 本质上是一串二进制字节流。这串字节流首先会到达服务器的网卡

网卡接收到电信号并将其转化为数字信号后,操作系统需要将这些数据转移到 Redis 程序能够访问的内存区域中。这个过程并不是自动完成的,它需要 CPU 的参与。CPU 需要执行一连串的指令,将网卡硬件缓冲区(这是网卡硬件自带的小内存,和系统内存无关)中的数据,复制到操作系统的内核内存中,然后再从内核内存复制到 Redis 程序的内存缓冲区中。这个过程,被称为 “网络 I/O 读取”

核心:

网卡硬件接收缓冲区(电信号→数字信号) → 操作系统内核态内存 → Redis用户态内存缓冲区

两次 CPU 参与的数据拷贝,将数据从网卡搬运到 Redis 程序可访问的内存中。

阶段二:内存指令执行

get name 这几个字符完整地存放在 Redis 的内存中后,Redis 开始解析这条命令。Redis 发现这是一个读取指令,目标键是 name

接下来,Redis 会在自己维护的内存数据结构(通常是一个巨大的哈希表)中去查找 name 这个键对应的值。由于所有的键值对都直接存储在计算机的内存中,CPU 只需要通过内存地址指针,直接读取到对应的数据。这个阶段完全是在内存内部进行查找和计算,不涉及任何外部硬件的交互。

阶段三:网络数据回写

假设 Redis 在内存中找到了 name 的值是 zhang san。现在,Redis 需要把这个结果返回给客户端。

这个过程是阶段一的逆向操作。Redis 程序不能直接把数据传递到网线上。它需要再次依靠 CPU,将存放在 Redis 内存中的结果数据 zhang san,复制到操作系统的内核空间,然后再由操作系统转移到网卡的发送缓冲区,最后由网卡将其转换为电信号发送出去。这个过程被称为 “网络 I/O 写入”

核心:

Redis用户态内存缓冲区 → 操作系统内核态内存 → 网卡硬件发送缓冲区(数字信号→电信号)

两次 CPU 参与的数据拷贝,将 Redis 的执行结果从程序内存搬运到网卡,最终发送给客户端。

阶段一和阶段三中的性能瓶颈

通过对上述三个阶段的分解可以明确:网络数据的接收与回写阶段,涉及了系统调用的触发、上下文切换以及大量由 CPU 驱动的内存拷贝操作。在网络通信中,数据的搬运是极其消耗 CPU 周期的操作。当系统的并发连接数不断增加时,大量的 CPU 时间被消耗在跨越内核空间与用户空间的数据复制上,而真正用于执行业务逻辑操作的 CPU 时间占比极低。数据复制所带来的延迟,构成了限制系统吞吐量进一步提升的核心瓶颈。

二、 Redis 早期单线程模型的执行机制与局限

在 Redis 6.0 之前,系统一直采用严格的单线程事件驱动架构。这一架构的基石是 I/O 多路复用技术,但在理解其工作原理时,需要严格界定 I/O 多路复用的功能边界。

1. I/O 多路复用机制的技术边界

简单来说,I/O 多路复用(如 Linux 中的 epoll)是一个高效的“网络事件监听器”。

当服务器面临 10000 个客户端连接时,传统的做法是开启 10000 个线程去分别死等这 10000 个连接发数据。这种方式会直接压垮操作系统。

I/O 多路复用的作用是:只需要一个单线程,就能同时监控这 10000 个连接。哪个连接有数据发过来了,它就会立刻通知 Redis:“连接 A 有数据到达了,连接 B 也有数据到达了。”

然而,必须强调的是,I/O 多路复用仅仅负责“状态通知”,它并不具备数据传输的能力。当 I/O 多路复用机制通知 Redis 有 100 个连接存在可读数据时,数据的读取操作(即前文提到的从内核空间拷贝到用户空间的过程),依然需要由 Redis 的那个唯一的主线程完成。

2. 单线程处理高并发的串行化逻辑

如果现在服务器上只有一个客户端发送了一个请求,那么单线程模型的表现是完美的:

  1. 主线程去网卡缓冲区把请求数据复制到内存(读)。
  2. 主线程在内存中执行查找操作(执行)。
  3. 主线程把结果数据从内存复制回网卡缓冲区(写)。

但是,在真实的生产环境中,Redis 面对的绝不是 1 个请求,而是成百上千个并发请求。假设现在有 100 个客户端同时发来了 get name 请求。

在单线程模型下,这唯一的 CPU 线程只能按照极其严格的顺序,排队处理这些请求。

  • 处理请求 1:读取数据 1(慢) -> 执行命令 1(极快) -> 写入结果 1(慢)
  • 处理请求 2:读取数据 2(慢) -> 执行命令 2(极快) -> 写入结果 2(慢)
  • 处理请求 3:读取数据 3(慢) -> 执行命令 3(极快) -> 写入结果 3(慢)
  • …(一直排队到第 100 个请求)

3. 单线程模型的资源利用率短板

通过上述流程,我们可以清晰地看到问题:

因为“读取数据”和“写入结果”这两个数据搬运工作非常耗时,导致这个单线程的绝大部分时间都在做“搬运工”。当线程仍在进行数据到搬运时,其它请求没有连接的机会,只能在网卡处等待。

尽管现代 CPU 有 8 核、16 核甚至 64 核,但因为 Redis 核心流只有这一个线程,导致其余的 63 个 CPU 核心都发挥不了作用。单核 CPU 的数据搬运能力是有物理极限的。当并发量大到一定程度,即使内存查找速度再快,这个唯一的线程也无法再快一秒地完成海量的数据复制操作,这就导致了整体吞吐量无法提升。

结论:单线程 Redis 的性能,受限于单核 CPU 复制网络数据的能力。

三、 Redis 6.0 多线程架构的技术重构

为了突破单核 CPU 数据拷贝能力的物理限制,Redis 6.0 在保留核心命令执行流单线程特性的基础上,对网络数据的处理模块进行了架构重构。其核心设计理念是:将消耗 CPU 资源的 I/O 数据读写操作从主线程中剥离,分配给多个独立的辅助线程并发执行;而对内存中复杂数据结构的操作,依然由主线程集中串行处理。

1. 并发处理架构的时序分解

当配置并开启多线程功能后,Redis 处理并发网络请求的事件循环被重构为包含明确同步屏障的多阶段协同工作流。

阶段一:并行读取数据

  1. I/O 多路复用机制探测到有 100 个网络连接的数据到达了网卡。(比如:100 个客户端同时发来了 get name 请求)。
  2. 主线程接收到这个情报后,不再像以前那样直接介入数据读取操作。
  3. 主线程将这 100 个连接平均分配到一个预先初始化的任务队列数组中。数组中的每一个队列对应一个专用的 I/O 工作线程(系统配置的线程总数中包含了主线程自身,因此主线程在分配完任务后,也会承担部分 I/O 工作)。
  4. 并行读取:这些个线程利用多核 CPU 的计算能力,同时开始执行数据搬运工作。它们一起调用操作系统的 read 指令,同时将数据从内核态拷贝到 Redis 的用户态内存中。
  5. 原本单线程需要按顺序搬运 100 次的过程,现在由多线程同时完成。

阶段二:串行执行命令

  1. 当这些 I/O 线程把所有的请求数据都读取完毕,并且解析成 Redis 能看懂的命令结构后,这些请求会被统一放在一个就绪队列中。
  2. 此时,所有的辅助 I/O 线程进入等待状态。
  3. 主线程接管工作,开始执行命令。主线程从队列中依次取出这 100 个 get name 请求,在内存中进行查找计算。
  4. 注意:这里的执行过程绝对是单线程、严格串行排队的 主线程执行完请求 1,再执行请求 2,直到请求 100 执行完毕。
  5. 为什么前面读得那么快,这里还要排队?因为内存执行速度极快,这 100 个请求的纯执行时间加起来可能也只有极其微小的瞬间,根本不需要等待。更重要的是,串行执行完全避免了数据冲突。这也是 Redis 单线程的核心思想。

阶段三:并行回写数据

  1. 主线程将 100 个请求全部执行完毕后,内存中生成了 100 份需要返回的响应数据(比如 100 个 zhang san 字符串)。
  2. 主线程再次将这 100 个发送任务分配给这些 I/O 线程。
  3. 再次并行:这些线程同时开始工作,并行地将这 100 份结果数据从 Redis 内存中复制回操作系统的网卡缓冲区。
  4. 数据全部发送完毕,一次并发处理周期结束,系统准备迎接下一批请求。

2. 并发控制与线程安全的底层逻辑

在对 Redis 6.0 多线程架构的分析中,一个关键的技术点是明确何种操作被并行化,何种操作被严格限制为串行

如果在执行具体命令(如对哈希表的修改、对有序集合元素的插入)时引入多核并行处理,势必会引发严重的数据竞争。为了保证多个线程同时修改同一数据块时不破坏数据结构的一致性,系统将不可避免地需要引入复杂的互斥锁(Mutex)或读写锁(RWLock)。锁机制的引入会导致线程在获取资源时发生阻塞等待,频繁的锁争用会引发大量的内核态上下文切换,严重增加系统开销;且细粒度的锁设计极易引发死锁风险,极大地增加了底层数据存储系统代码的复杂度和维护难度。

因此,Redis 6.0 中所有的网络 I/O 读取、数据内存拷贝等纯粹消耗 CPU 运算周期但不涉及核心共享数据结构的操作,均被下放给多线程并行执行;而实际操作全局键值对数据结构、执行核心业务逻辑的步骤,依然被强制锁定在单线程环境中依次执行。

这种架构设计在确保 Redis 内部复杂数据结构无需加锁、保持数据状态绝对安全一致的同时,利用多核处理器的并发能力显著缩短了网络数据的搬运时间窗口,从而提升了系统在单位时间内的总指令吞吐量。

四、 线程执行顺序保证

在多线程并发读取时,由于各线程处理速度不一,先读取完毕的连接数据是否会被主线程优先执行?

所有的读取在并发阶段是互相独立的,但必须全部彻底完成后,才会集中汇聚到主线程的执行队列中。主线程随后依据特定的算法规则(通常是文件描述符的事件触发顺序或分配顺序)对就绪任务进行排队并依次处理。这种设计牺牲了极其微小的一段等待时间(因各线程处理速度差异造成的短板效应),但换取了整个系统指令执行顺序的强一致性。

五、 Redis 6.0 多线程配置参数

理解了理论基础,我们再来看看这套机制在实际应用中的配置。Redis 在引入多线程时表现得极其严谨,默认情况下,这项功能是处于关闭状态的

这是因为在大部分常规的业务场景中,由于并发量未达到极限,单纯利用单核的 I/O 多路复用以及主线程的处理,已经能够支撑每秒数万甚至十万级的并发请求。

当服务器的网络带宽成为真正的瓶颈时,可以通过修改配置文件(redis.conf)来启用并调节多线程的运行状态。涉及到的核心配置参数主要有两个:

1. io-threads 配置

该参数决定了 Redis 启动多少个线程来处理 I/O 搬运工作(这个数字包含了主线程本身)。

例如,配置 io-threads 4,意味着 Redis 将分配 3 个专门的辅助线程去处理网络数据的读写操作,再加上主线程协同工作。

设定这个数值需要参考服务器物理 CPU 的核心数量。官方技术文档给出的指导原则是:

  • 如果服务器拥有 4 个物理核心,建议将 I/O 线程数设置为 2 或 3,预留一个核心给操作系统的其他进程。
  • 如果服务器拥有 8 个物理核心,建议设置为 6。
  • 尤为重要的是,配置超过 8 个以上的 I/O 线程通常毫无意义。因为当 I/O 搬运速度快到一定程度后,唯一的瓶颈就会不可避免地转移到主线程的单核执行速度上。一旦主线程处理命令的速度跟不上 I/O 线程塞入请求的速度,再多增加 I/O 搬运工也没有用,反而会徒增线程切换的系统负担。

2. io-threads-do-reads 配置

这是一个更进一步的精细化控制参数。它的默认值是 no

在默认的开启策略下(即 io-threads > 1,但此参数为 no),Redis 的多线程仅仅作用于网络发送数据(网络写)阶段,而在接收请求数据(网络读)阶段,依然由主线程独自承担。

为什么要有如此细致的设计?这源于对真实网络流量模型的统计分析。

在绝大多数数据库的应用场景中,客户端发出的请求通常只包含简短的命令字和少量参数(比如 get extremely_long_key_name),这个请求包是非常小的,读取成本相对较低。然而,服务端返回的结果却可能极其庞大(比如返回一个包含了成千上万个元素的复杂集合),将这些巨大的数据从内存拷贝到网卡,其耗时远远大于读取请求。

因此,单单把最耗时的“写响应数据”阶段进行多线程并行化,就已经能够解决大部分的网络瓶颈问题。只有在系统面临极端的高频、大包写入压力时,才建议将 io-threads-do-reads 设置为 yes,彻底开启双向的多线程 I/O 并行。

六、 总结

随着网络带宽和并发量的提升,限制 Redis 单机吞吐量的核心因素不再是内存中指令的执行速度,而是跨越操作系统内核与用户空间搬运网络数据的 CPU 消耗。多线程的加入,正是为了利用多核 CPU 的并行能力,加速网络请求数据的读取与响应数据的回写。

该架构的核心在于“任务分离”。系统将纯粹消耗 CPU 算力且耗时的网络 I/O 操作剥离给辅助线程并发处理,而对核心内存数据结构的操作依然坚守单线程串行执行。这种设计既有效缩短了数据在网络层面的等待时间,又完全规避了并发操作引入的锁竞争与死锁风险,确保了数据的一致性与高并发下的处理效率。同时,依然依赖 I/O 多路复用机制进行事件监听,并通过严格的同步等待机制,保证了所有并发读取的数据最终能够按既定顺序交由主线程统一处理。。

最后,Redis 在配置策略上保持了严格的克制。多线程功能默认关闭,且在开启时默认仅作用于数据量较大的网络写阶段。这表明多线程机制并非适用于所有业务场景的通用手段,而是专为突破特定网络 I/O 瓶颈而提供的扩展能力。使用者需要根据实际的物理硬件资源与网络流量特征,合理评估并开启相应的配置。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐