Redis 为什么这么快

作者:杨帅      发布日期:2026.07.17

性能 · 原理

Redis 为什么这么快

单机十万级 QPS 的背后:内存、单线程、IO 多路复用与高效数据结构的合力

"快"到底有多快

在单机、单实例、普通命令(如 GET/SET)的场景下,Redis 轻松跑到每秒十万级 QPS,单条命令的延迟通常在微秒到几十微秒量级。相比之下,一次普通的磁盘随机 IO 是毫秒级——两者相差成千上万倍。

但"快"并不是某个单点魔法,而是从存储介质、线程模型、网络处理、数据结构到通信协议层层优化叠加出来的结果。这篇文章就把这些支柱一个个拆开来看。

五大提速支柱

Redis 高性能 = 五根支柱共同支撑 低延迟 · 高吞吐 纯内存 单线程 IO 多路复用 高效数据结构 紧凑协议
图 1 · 支撑 Redis 高性能的五根支柱

一、纯内存操作

Redis 把所有数据都放在内存里,读写直接命中 RAM,绕开了磁盘 IO 这个最大的瓶颈。内存的随机访问延迟是纳秒级,而机械磁盘是毫秒级,即便是 SSD 也在微秒级——这是 Redis 快的最根本原因。

不同介质的随机访问延迟(对数量级示意) 内存 (RAM) ~100 纳秒 SSD ~100 微秒(约 1000×) 机械磁盘 ~10 毫秒(约 100000×) 数据量级为示意,实际数值随硬件而异
图 2 · 内存 vs 磁盘:Redis 快的物理基础
代价:内存又贵又有限,且断电即失。所以 Redis 需要持久化(RDB/AOF)来兜底,也需要合理的内存淘汰策略(如 LRU/LFU)来控制容量——"快"是用"贵"换来的。

二、单线程模型

很多人第一次听说"Redis 是单线程"会感到意外:单线程怎么可能快?关键在于——Redis 的核心命令处理是单线程的,这恰恰避开了多线程最大的开销。

  • 没有锁竞争:所有命令串行执行,天然线程安全,不需要加锁、不会有死锁。
  • 没有上下文切换:省去了线程调度和 CPU 缓存失效的开销。
  • 实现简单可控:命令的原子性由单线程天然保证,代码路径短。

由于瓶颈主要在内存和网络 IO而非 CPU 计算,单线程已经足够榨干单核性能,多线程带来的并发收益反而被锁和切换成本抵消。

三、IO 多路复用与 Reactor

单线程要同时服务成千上万个客户端连接,靠的是IO 多路复用(epoll / kqueue)。一个线程通过一次系统调用就能监听大量连接的就绪事件,只处理"真正有数据可读写"的连接,避免了为每个连接开一个线程/进程的巨大开销。

client 1 client 2 client 3 client N IO 多路复用器 epoll / kqueue 监听所有连接就绪事件 就绪事件队列 仅含有数据的连接 单线程 事件循环 逐个处理命令
图 3 · Reactor 模型:一个线程 + 多路复用服务海量连接

这套"事件驱动 + 非阻塞 IO"的组合就是经典的 Reactor 模型:主线程在事件循环里不停地"取就绪事件 → 执行对应命令 → 返回响应",全程不阻塞在任何单个连接上。

四、精心设计的数据结构

Redis 对外暴露的每种数据类型,底层都有多种编码(encoding),会根据数据规模自动切换,在内存占用和操作复杂度之间取最优。

对外类型底层编码切换/用途
Stringint / embstr / raw整数、短字符串、长字符串分别用不同编码省内存
Hashlistpack / hashtable字段少时用紧凑数组,超阈值转哈希表
Listquicklist(listpack 链表)兼顾内存紧凑与两端 O(1) 操作
Setintset / listpack / hashtable纯整数用 intset,小集合用紧凑结构
ZSetlistpack / skiplist + hashtable跳表实现 O(log N) 范围查询,哈希表 O(1) 查分值

以有序集合 ZSet 为例,它同时用跳表支持按分值范围查询和排名,又用哈希表支持按成员 O(1) 取分值——用空间换时间,让不同访问模式都快。全局哈希表还配合渐进式 rehash,把扩容开销分摊到多次操作里,避免一次性大停顿。

五、协议与编码上的极致优化

Redis 使用自定义的 RESP(REdis Serialization Protocol)协议:文本为主、结构简单、解析极快,不像 XML/JSON 那样需要复杂解析。配合以下手段进一步压榨吞吐:

  • Pipelining(管道):客户端一次性批量发送多条命令,减少网络往返(RTT),吞吐可提升数倍。
  • 批量命令MGET/MSET/HMGET 等一次操作多个 key,减少命令条数。
  • 连接复用:客户端连接池避免频繁建连的开销。
逐条请求(多次 RTT) Pipeline(一次 RTT) 客户端 服务端 客户端 服务端 3 条命令一起发
图 4 · Pipeline 把多次网络往返压缩成一次,大幅提升吞吐

补充:6.0 之后的多线程

Redis 6.0 引入了多线程 IO,但要澄清一个常见误解:多线程只用于网络读写(协议的读取与回复的发送)这些耗时的 IO 环节,命令的实际执行仍然是单线程的。这样既保留了单线程"无锁、原子、简单"的优点,又在高并发大流量下把网络 IO 的瓶颈用多核分担掉。

一句话记忆:"数据操作单线程,网络 IO 多线程。" 命令执行的串行性和原子性从未改变。

别把快用成慢:常见性能陷阱

正因为核心是单线程,一条慢命令会阻塞所有其他请求。实践中要特别注意:

  • 大 key:一个巨大的 Hash/Set/ZSet 在删除或序列化时会长时间占用主线程,导致其他请求排队。
  • O(N) 命令:生产环境慎用 KEYS *HGETALL、大范围 SMEMBERS,改用 SCAN 系列渐进遍历。
  • 复杂 Lua 脚本:脚本执行期间同样阻塞主线程,要控制耗时。
  • 集中过期:大量 key 同一时刻过期会造成瞬时抖动,可加随机偏移打散。

小结

Redis 的"快"是一套系统性设计的合力:纯内存提供物理上的低延迟,单线程省掉锁与上下文切换,IO 多路复用让一个线程扛住海量连接,高效数据结构让每种操作都尽量低复杂度,紧凑协议与 Pipeline把网络开销压到最低。理解这些原理,不仅能解释它为什么快,更能帮你避开"大 key、O(N) 命令"这类会亲手把它变慢的坑。