Redis 的存取速度为什么这么快

📅 2026/6/25 12:05:20 ✍️ 编辑团队 👁️ 阅读次数
Redis 的存取速度为什么这么快
Redis 的存取速度为什么这么快目录因素一内存不是磁盘因素二高效的数据结构因素三单线程反而更快因素四IO 多路复用因素五RESP 协议够简单常见误区小结redis常常用于缓存是因为它的存取速度非常之快它比mysql快特别多有多快一条 SQL 查询用户信息执行时间 200ms。换成 Redis 的GET user:1001耗时 0.1ms。同样是从存储里取一个值差了 2000 倍。你可能有听过Redis 用的内存MySQL 用的磁盘所以快。这个回答是正确的但只对了五分之一。Redis 快不是单一因素的结果而是五个设计决策叠加出来的。本篇文章就从这五个因素来探讨一下Redis为什么这么快。因素一存取于内存这是最直观、最主要的一个因素也是所有其他因素的前提。CPU 访问内存大约需要 100 纳秒访问磁盘需要 10 毫秒。差了十万倍。MySQL 的数据存在磁盘上。即使有 Buffer Pool 缓存热数据但 Buffer Pool 的大小有限不可能把整张表都放进去。一旦查询的数据不在缓存中就得触发磁盘 IO。Redis 把所有数据都放在内存里。客户端发来一个GET命令Redis 直接从内存中读取值返回没有任何磁盘 IO 的参与。这就是 0.1ms 级响应时间的基础。但在内存里只是入场券。同样是内存数据库设计不好一样慢。接下来的四个因素决定了 Redis 在内存之上能做到多快。因素二高效的数据结构Redis 不是一个简单的 key-value 缓存。它支持 String、Hash、List、Set、ZSet 等多种数据类型每种类型底层都用了专门优化过的数据结构。这些数据结构决定了操作的时间复杂度而时间复杂度决定了性能上限。以 String 类型为例Redis 没有直接用 C 语言原生的字符串char*而是自己设计了 SDSSimple Dynamic String。SDS 多存了一个len字段记录字符串长度获取长度的操作从 O(n) 变成了 O(1)。同时 SDS 预分配了多余空间追加字符串时不需要每次都重新分配内存。再看 Hash 类型。当字段数量较少时Redis 用 ziplist压缩列表存储所有数据紧凑排列在一块连续内存中节省空间。当字段数量超过阈值自动转成哈希表查找复杂度是 O(1)。最值得一提的是 ZSet有序集合。ZSet 需要同时支持按 score 排序和按 member 查找两个需求Redis 用了两个数据结构组合实现哈希表member → score 的映射O(1) 查分值跳表按 score 排序的多层链表支持 O(log n) 的范围查询因素三单线程处理Redis 处理命令是单线程的。同一时刻只有一个命令在执行。这听起来很反直觉。单线程不是应该更慢吗为什么不用多线程让多个命令同时执行因为Redis的瓶颈不在 CPU而在网络 IO 和内存访问。Redis 的每个命令执行时间是纳秒到微秒级别CPU 根本不是瓶颈。真正花时间的是等客户端的请求通过网络到达。多线程不但帮不上忙反而会引入额外开销1. 锁竞争多线程同时修改一个哈希表必须加锁。加锁意味着线程要等、要争抢这些等待时间可能比命令本身的执行时间还长。2. 上下文切换CPU 在不同线程之间切换时需要保存和恢复寄存器、缓存等上下文信息。这个过程本身就有开销而且会导致 CPU 缓存失效增加内存访问延迟。3. 数据结构复杂度上升为了让数据结构线程安全要么加锁要么用无锁算法CAS。前者影响性能后者增加代码复杂度。单线程下这些都不需要考虑。单线程的好处是显而易见的没有锁、没有上下文切换、代码简单、调试方便。Redis 的作者 antirez 说过单线程是他做出的最正确的设计决策之一。Redis 6.0 时引入了多线程 IO但命令执行仍然是单线程。多线程只负责网络数据的读写把数据从 socket 读出来、把响应写回 socket。命令执行那一步还是单线程串行。这说明 Redis 团队也认为命令执行用单线程是正确的瓶颈已经转移到了网络 IO 层面。因素四IO 多路复用Redis使用多路复用的IO模型使其可以单线程就处理上万个客户端连接。传统的阻塞 IO 模型下一个线程处理一个连接。线程调用read()等数据到达如果没有数据线程就阻塞着干不了别的。要同时处理 1000 个连接就得 1000 个线程。IO 多路复用改变了这个模型。它的核心思想是一个线程同时监听多个连接哪个连接有数据到达就处理哪个其他的继续等。在 Linux 上Redis 用epoll实现 IO 多路复用。epoll_wait()会返回所有就绪的连接列表Redis 遍历这个列表依次执行每个连接发来的命令。因为命令执行本身很快微秒级所以这个遍历过程也很快一个线程就能撑住数万并发连接。这也是为什么 Redis 的性能瓶颈通常不在 CPU而在网络。单线程处理命令绰绰有余但网络带宽和延迟是有上限的。所以生产环境中Redis 通常部署在内网用连接池复用连接减少网络开销。因素五RESP 协议够简单Redis 和客户端之间通信用的是 RESPRedis Serialization Protocol协议。这个协议设计得极其简单OK\r\n → 简单字符串 -ERR unknown command\r\n → 错误 :1000\r\n → 整数 $5\r\nhello\r\n → 批量字符串5字节 *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n → 数组客户端发给 Redis 的命令本质上就是一个字符串数组。比如GET user:1001序列化后就是*2\r\n$3\r\nGET\r\n$10\r\nuser:1001\r\n解析过程非常简单按行读、读到$就读长度、再按长度读数据。不需要复杂的状态机不需要处理嵌套结构解析成本极低。对比一下 HTTP 协议。一个 HTTP 请求要解析请求行、请求头、空行、请求体请求头是键值对但格式灵活还有各种编码方式chunked、gzip。解析一个 HTTP 请求的开销远大于解析一个 RESP 请求。协议越简单解析越快CPU 开销越小。RESP 就是专门为 Redis 的使用场景设计的——命令都是简单的字符串数组不需要 HTTP 那么强的表达能力。小结Redis 快的本质是在内存之上用最合适的工具做最合适的事。高效的数据结构保证了操作的时间复杂度接近理论最优单线程模型避免了锁竞争和上下文切换的开销IO 多路复用让一个线程就能管理成千上万的连接RESP 协议把通信解析的成本压到最低。这五个因素不是孤立的它们互相配合构成了一个没有明显短板的系统。