为什么 Redis 单线程却比多线程还快?真实原因震撼我

65 阅读8分钟



大家好,我是小米,一个在互联网研发里滚了十来年、对技术既执着又好奇、写代码比写周报开心的 31 岁男人。

今天这篇文章想和你聊聊一个面试“高频炸裂”的问题——Redis 线程模型到底是怎么回事?

别小看这个问题,它比你想象的“坏”多了。

有一次我去一家还算知名的互联网公司面试,面试官是个戴着黑框眼镜、看起来很斯文的小哥,但当他一开口,我立马意识到这人不好对付。

他慢慢抬头看我一眼,淡定问道:

“我们聊点简单的吧。Redis 是单线程吗?为什么单线程还能这么快?它的线程模型你能讲讲吗?”

这一瞬间,我脑袋里警报大响:完了,这是送命题。

不过没办法,程序员嘛,刀山火海也得硬冲。于是我脑内迅速召唤出我多年研究 Redis 的记忆碎片,然后把故事讲了出来。

今天我就把这套“面试官听了会满意,你听了会拍大腿”的 Redis 线程模型解析,用轻松、故事化的方式讲给你听。

你看完以后,下次面试官问这个,你直接微微一笑,然后把本文的精髓娓娓道来,稳了。

Redis 线程模型不是“单线程”这么简单

很多人对 Redis 的第一印象就是——

“Redis 是单线程的。”

这句话其实对,也不对。正确来说:

  • Redis 的核心逻辑处理是单线程: 也就是处理命令的主线程只有一个。
  • Redis 并不是只有一个线程: IO、持久化、集群、异步删除、过期清理,这些都有辅助线程。

那它为什么要“装”成单线程?这是因为 Redis 的核心哲学是:快。

越简单,越能快得起来。但是!它到底怎么做到“一线程干掉多线程”?这就得讲一个行业传奇的故事了。

故事从一次我面试时的“尴尬瞬间”说起

那天面试官继续问我:

“Redis 单线程为什么性能这么高?”

我心里想着:

兄弟你知道这问题有多大吗?这背后涉及 IO 模型、数据结构、内存布局、CPU Cache、锁竞争、系统调用、事件循环……

但没办法,我又不能回他一句“因为够简单”,于是我深吸一口气开始讲故事:

Redis 线程模型的核心:Reactor 单线程事件循环

Redis 的主线程使用的是 基于 Reactor 模式的事件驱动机制。简单比喻一下:

Redis 的主线程就像餐馆里 一位非常高效的厨师

  • 所有点单都由他接收
  • 所有炒菜他一个人完成
  • 他速度极快,手法干净利落
  • 菜都是提前准备好的,锅有预热,酱料放好
  • 没有另外一个厨师跟他抢锅
  • 没有人跟他抢勺子

所以:

他一个人反而比三个厨师互相碰锅、抢位置搞得更快。

Redis 主线程干的活其实就三件:

  • 读:接收 socket 的数据(比如 SET、GET、HSET 指令)
  • 处理:执行具体命令逻辑(用 C 写的操作内存的代码,快得飞起)
  • 写:把结果返回给客户端

这一套动作全部在 一个线程的事件循环(Event Loop)里完成。事件循环大概是这样的:

是不是非常 Node.js?没错,Redis 和 Node.js 都是典型的基于 Reactor 的事件驱动模型。

为什么单线程还能这么快?

当我把上面的结构说给面试官听后,他满意地点点头,然后继续发问:

“那为什么 Redis 单线程却比线程池模型还快?”

你看,这才真正进入灵魂。于是我继续解释:

1. Redis 完全基于内存操作

内存是纳秒级的。磁盘是毫秒级的。差了 100 万倍。你说快不快?

2. 单线程避免上下文切换

线程多了意味着:

  • CPU 上下文切换
  • 内核态 ↔ 用户态频繁切换
  • 线程调度带来的 cache miss

而这些开销其实非常大。Redis 一条线程处理所有逻辑(不含持久化),没有切换成本,效率自然高得吓人。

3. 使用高效 IO 多路复用

Redis 使用:

  • epoll(Linux)
  • kqueue(BSD)
  • select(旧系统)

这让 Redis 能够:

在单线程中同时管理成千上万个客户端连接。

4. 数据结构设计极其优秀

Redis 内部的数据结构全部是 C 实现的:

  • dict(哈希表)
  • skiplist(跳表)
  • sds(字符串)
  • quicklist、ziplist、listpack
  • radix tree

这些结构都是为性能优化过的:

  • 避免额外指针
  • 连续内存布局
  • 减少 malloc
  • 减少 cache miss
  • O(1) 或 O(log n) 时间复杂度

CPU Cache 命中率高,性能自然爆炸。

5. 命令本身非常高效

Redis 的所有命令都是原子操作,C 函数一次干到底。不像数据库还要:

  • SQL 解析
  • SQL 优化
  • 锁管理
  • 事务协调
  • 权限检查

Redis 直接干,不废话。

6. 功能分线程,不干扰主线程

虽然主线程单线程,但其他耗时任务都被“外包”了:

主线程只处理核心逻辑,不被拖慢。所以 Redis 才能做到:

单线程,却比大部分多线程系统更快。

Redis 真正的线程模型(从 Redis 6 开始)

面试官听完我一大段输出后,突然问了一个升级版问题:

“那 Redis 6 呢?还是单线程吗?”

好家伙,这人绝对是想把我拿下。于是我继续解释:

Redis 6 有多线程,但不处理命令

Redis 6 引入了 I/O 多线程

  • 接收网络请求:多线程
  • 发送响应:多线程
  • 命令执行:依旧单线程

为什么?因为 Redis 的核心命令处理继续保持单线程的设计理念:

  • 不用加锁
  • 不用事务冲突
  • 没有竞争
  • 没有抢占

但网络 I/O 是可以多线程并发的,所以引入了多线程 I/O 来提升吞吐。

一句话总结:

Redis 6:I/O 多线程,命令单线程。

完整总结:Redis 线程模型的全景结构图

如果我把 Redis 做成一张组织架构表,大概是这样的:

总结一句话:

Redis 的“单线程”指的是:命令处理线程是单线程。但整个 Redis 不是单线程。

如何向面试官完美回答 Redis 线程模型?

下面给你一个“面试必杀版答案”,你背下来就赢了:

标准版(两分钟)

Redis 的核心命令处理是单线程,通过基于 Reactor 的事件循环模型来处理网络事件、命令执行和响应输出。

它的性能高主要依赖于:纯内存操作、高效数据结构、IO 多路复用、避免线程切换、命令简单高效。

虽然核心逻辑是单线程,但 Redis 内部还有其他线程处理异步删除、AOF 刷盘、RDB 持久化等耗时操作,从而保证主线程不被阻塞。

从 Redis 6 开始引入了 I/O 多线程,用于网络读写加速,但命令执行仍保持单线程,以避免加锁和竞争。

面试官听了会很舒服的版本(10 秒钟)

Redis 的命令执行是单线程的,但 Redis 不是单线程系统。

它使用事件驱动模型执行命令,同时把耗时任务丢给后台线程。

Redis 6 之后网络 I/O 支持多线程,但核心逻辑仍然单线程。

简单 + 内存 + 高效数据结构 = 它特别快。

故事的结尾:面试官点头,我拿到了 Offer

当我把这些讲完之后,面试官露出满意的微笑:

“讲得不错,可以。”

那一刻,我偷偷握紧拳头:

果然,技术实力才是最硬的底气。

后来我也顺利拿到了 Offer。

Redis 的线程模型看似简单,但背后是一整套极致追求性能的架构哲学:

  • 单线程处理核心逻辑
  • 多线程处理耗时任务
  • 内存存储
  • 高效数据结构
  • IO 多路复用
  • 零锁模型
  • 最大程度利用 CPU Cache

真正的工程之美就在这里。

END

如果你看到这里,恭喜你,你完全具备了在面试中自信讲解 Redis 线程模型的能力。以后面试官再问你:

“Redis 是不是单线程?为什么这么快?线程模型怎么实现的?”

你只需要轻轻一笑:

“要不我给你讲讲 Redis 的 Reactor 模型?”

“顺便聊聊 Redis 6 的多线程 I/O?”

“要不连 IO 多路复用我也给你带一遍?”

稳得不能再稳。

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!