Redis 为什么这么快
"快"到底有多快
在单机、单实例、普通命令(如 GET/SET)的场景下,Redis 轻松跑到每秒十万级 QPS,单条命令的延迟通常在微秒到几十微秒量级。相比之下,一次普通的磁盘随机 IO 是毫秒级——两者相差成千上万倍。
但"快"并不是某个单点魔法,而是从存储介质、线程模型、网络处理、数据结构到通信协议层层优化叠加出来的结果。这篇文章就把这些支柱一个个拆开来看。
五大提速支柱
一、纯内存操作
Redis 把所有数据都放在内存里,读写直接命中 RAM,绕开了磁盘 IO 这个最大的瓶颈。内存的随机访问延迟是纳秒级,而机械磁盘是毫秒级,即便是 SSD 也在微秒级——这是 Redis 快的最根本原因。
二、单线程模型
很多人第一次听说"Redis 是单线程"会感到意外:单线程怎么可能快?关键在于——Redis 的核心命令处理是单线程的,这恰恰避开了多线程最大的开销。
- 没有锁竞争:所有命令串行执行,天然线程安全,不需要加锁、不会有死锁。
- 没有上下文切换:省去了线程调度和 CPU 缓存失效的开销。
- 实现简单可控:命令的原子性由单线程天然保证,代码路径短。
由于瓶颈主要在内存和网络 IO而非 CPU 计算,单线程已经足够榨干单核性能,多线程带来的并发收益反而被锁和切换成本抵消。
三、IO 多路复用与 Reactor
单线程要同时服务成千上万个客户端连接,靠的是IO 多路复用(epoll / kqueue)。一个线程通过一次系统调用就能监听大量连接的就绪事件,只处理"真正有数据可读写"的连接,避免了为每个连接开一个线程/进程的巨大开销。
这套"事件驱动 + 非阻塞 IO"的组合就是经典的 Reactor 模型:主线程在事件循环里不停地"取就绪事件 → 执行对应命令 → 返回响应",全程不阻塞在任何单个连接上。
四、精心设计的数据结构
Redis 对外暴露的每种数据类型,底层都有多种编码(encoding),会根据数据规模自动切换,在内存占用和操作复杂度之间取最优。
| 对外类型 | 底层编码 | 切换/用途 |
|---|---|---|
| String | int / embstr / raw | 整数、短字符串、长字符串分别用不同编码省内存 |
| Hash | listpack / hashtable | 字段少时用紧凑数组,超阈值转哈希表 |
| List | quicklist(listpack 链表) | 兼顾内存紧凑与两端 O(1) 操作 |
| Set | intset / listpack / hashtable | 纯整数用 intset,小集合用紧凑结构 |
| ZSet | listpack / skiplist + hashtable | 跳表实现 O(log N) 范围查询,哈希表 O(1) 查分值 |
以有序集合 ZSet 为例,它同时用跳表支持按分值范围查询和排名,又用哈希表支持按成员 O(1) 取分值——用空间换时间,让不同访问模式都快。全局哈希表还配合渐进式 rehash,把扩容开销分摊到多次操作里,避免一次性大停顿。
五、协议与编码上的极致优化
Redis 使用自定义的 RESP(REdis Serialization Protocol)协议:文本为主、结构简单、解析极快,不像 XML/JSON 那样需要复杂解析。配合以下手段进一步压榨吞吐:
- Pipelining(管道):客户端一次性批量发送多条命令,减少网络往返(RTT),吞吐可提升数倍。
- 批量命令:
MGET/MSET/HMGET等一次操作多个 key,减少命令条数。 - 连接复用:客户端连接池避免频繁建连的开销。
补充:6.0 之后的多线程
Redis 6.0 引入了多线程 IO,但要澄清一个常见误解:多线程只用于网络读写(协议的读取与回复的发送)这些耗时的 IO 环节,命令的实际执行仍然是单线程的。这样既保留了单线程"无锁、原子、简单"的优点,又在高并发大流量下把网络 IO 的瓶颈用多核分担掉。
别把快用成慢:常见性能陷阱
正因为核心是单线程,一条慢命令会阻塞所有其他请求。实践中要特别注意:
- 大 key:一个巨大的 Hash/Set/ZSet 在删除或序列化时会长时间占用主线程,导致其他请求排队。
- O(N) 命令:生产环境慎用
KEYS *、HGETALL、大范围SMEMBERS,改用SCAN系列渐进遍历。 - 复杂 Lua 脚本:脚本执行期间同样阻塞主线程,要控制耗时。
- 集中过期:大量 key 同一时刻过期会造成瞬时抖动,可加随机偏移打散。
小结
Redis 的"快"是一套系统性设计的合力:纯内存提供物理上的低延迟,单线程省掉锁与上下文切换,IO 多路复用让一个线程扛住海量连接,高效数据结构让每种操作都尽量低复杂度,紧凑协议与 Pipeline把网络开销压到最低。理解这些原理,不仅能解释它为什么快,更能帮你避开"大 key、O(N) 命令"这类会亲手把它变慢的坑。


