Skip to content

为什么 Redis 设计为单线程?6.0 版本为何引入多线程? ​

  • 标签:后端、Redis

🏛 业务架构师 ​

Redis 的选择是教科书级的"按瓶颈演进":

为什么单线程(命令执行层):

  • Redis 的性能瓶颈不在 CPU 计算,在内存和网络 IO——命令都是内存操作,执行极快,多线程的收益抵不过锁竞争和上下文切换的成本;
  • 单线程带来无锁、时序确定、实现简单三个红利:不用考虑并发修改数据结构,排查问题逻辑线性,运维心智成本极低——这对"Redis 出问题全站抖"的基础设施来说是核心优势;
  • 配合 IO 多路复用,单线程也能扛数十万 QPS。

6.0 为什么引入多线程:网络流量涨到 10G/25G 网卡后,读写 socket 和协议解析成了新瓶颈(单线程处理网络包吃满一个核)。于是把网络 IO 与协议解析并行化,命令执行仍然单线程——性能提升约一倍,同时保住了"无锁"这个根本优势。

业务架构的启示三条:

  1. 优化要打在当前瓶颈上:CPU 不是瓶颈就别急着多线程,Redis 单线程扛了十年主流场景——先测量再动手,反对"看起来该快就加线程"的本能;
  2. 简单性是可衡量的资产:单线程省下的锁复杂度,兑换成了 Redis 十几年的稳定口碑。我做业务架构同样会算"并发复杂度成本"这笔账;
  3. 演进保持核心不变:多线程只动 IO 层不动数据层,"命令串行执行"的语义契约没变,客户端和业务零迁移成本——好的演进是不破坏契约的演进。

🏗 系统架构师 ​

为什么单线程:Redis 的性能瓶颈不在 CPU,而在内存和网络带宽。命令执行本身是纯内存操作,纳秒到微秒级,单线程足够把内存打满;单线程换来三个红利:无锁无竞争(不用线程同步)、无上下文切换开销、实现简单且天然保证命令级原子性——这是它能做复杂事务/Lua 的根基。

6.0 为什么引入多线程:QPS 到 10 万以上后,瓶颈移到网络 IO:大 value 场景下 read/write 系统调用和协议解析占了大头,单核跑满也追不上网卡。所以 6.0 的多线程只做 socket 读写与协议解析/回包,命令仍由主线程串行执行——既榨干多核网络处理能力,又不动命令原子性的根基。这和我在网关架构里"IO 线程池 + 业务线程池"分离是同一个思想:并行化 IO 层,串行化状态变更层。

单线程的运维红线:任何阻塞命令都是全局停顿——keys *、flushall、大 key 操作、复杂 Lua、AOF fsync 策略。我的规范:线上禁用阻塞命令、大 key 拆分、删除用 UNLINK、慢查询监控 + latency monitor 常开,保证单线程模型的纯度。

💻 全栈开发专家 ​

为什么单线程(指命令执行线程):

  1. 瓶颈定位:Redis 操作都在内存,单机瓶颈是内存容量和网络带宽,CPU 几乎不是瓶颈——单线程够用;
  2. 无锁:所有命令串行执行,不需要任何锁和同步开销,也没有线程切换成本,还天然保证单命令原子性(Lua/事务的原子性就建立在这上面);
  3. 实现简单可靠:数据结构的并发语义简单,出 bug 概率低。

6.0 为什么引入多线程:高 QPS + 大 value 场景下,网络 IO 的读写和协议解析占了大量 CPU(读写 socket、拷贝数据),单核被打满,而命令执行本身很轻。所以 6.0 的多线程只分担网络 IO:

text
IO 线程池:读请求/解析协议、写响应/序列化   ← 可多线程
主线程:   逐条执行命令                    ← 仍然单线程
ini
# redis.conf
io-threads 4
io-threads-do-reads yes   # 一般 4 核以上才开

我的实测结论:小 value 高 QPS 场景 8.0 默认(读也多线程化)提升明显;命令执行仍串行,所以*慢命令(keys 、大 key 的 hgetall)照样卡全局——多线程救不了慢查询,治理慢查询和 bigkey 依然是第一优先级。