Redis 单线程,为什么还这么快?
结论:快不是因为"单线程"这个标签,而是四个条件同时成立:数据在内存里、访问路径上没有锁、IO 用多路复用不阻塞、数据结构与协议都足够省。单线程是结果而不是原因——正因为命令串行,才敢去掉所有并发控制,把每一次操作压到接近纯内存拷贝的开销。
一、性能账本:一次 GET 花在哪
| 环节 | 典型耗时 | 优化手段 |
|---|---|---|
| 网络往返(同机房) | 0.1~0.3 ms | Pipeline、批量命令、就近部署 |
| epoll 事件分发 | 微秒级 | 多路复用,不为连接建线程 |
| 协议解析(RESP) | 微秒级 | 协议极简,仅前缀 + 长度 |
| 哈希表定位 | 纳秒~微秒 | 渐进式 rehash,摊还 O(1) |
| 内存拷贝与回写 | 纳秒~微秒 | 避免大 value,控制单次响应体量 |
可以看到,真正的瓶颈是网络往返,不是 CPU。这也解释了为什么 Redis 的高性能优化手段大多是"减少往返次数"(Pipeline、Lua、批量命令),而不是"让命令跑得更快"。
二、四个支柱
┌────────────────────────────────────────┐
快的原因 │ 1. 纯内存:无磁盘 IO,无 B+ 树页读取 │
│ 2. 单线程:无锁、无上下文切换、无竞态 │
│ 3. epoll:一个线程管上万连接,事件驱动 │
│ 4. 数据结构:SDS/跳表/listpack 为场景定制 │
└────────────────────────────────────────┘
│
共同结果:单次操作 ≈ 内存访问 + 一次网络往返
- 纯内存:不涉及磁盘随机读,操作耗时稳定在百纳秒级,没有 IO 抖动。
- 单线程无锁:不用考虑锁竞争、死锁、线程切换,也不存在多线程下"缓存行伪共享"的隐性开销。
- IO 多路复用:epoll 只返回就绪的 fd,主线程按序处理,连接数与线程数解耦。
- 定制数据结构:SDS 记录长度使 strlen 为 O(1),跳表让范围查询为 O(log N),listpack 把小集合压进连续内存。
三、Pipeline:把 RTT 摊薄的正确姿势
无 Pipeline: C→S 命令1 S→C 结果1 C→S 命令2 S→C 结果2 (2 次 RTT)
有 Pipeline: C→S 命令1+命令2+命令3 S→C 结果1+2+3 (1 次 RTT)
// Lettuce:一次网络往返发送 1000 条命令
RedisAsyncCommands<String, String> async = connection.async();
async.setAutoFlushCommands(false);
for (int i = 0; i < 1000; i++) {
async.set("k:" + i, "v" + i);
}
async.flushCommands(); // 真正发出
# 命令行验证 Pipeline 与逐条执行的差距
redis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -c 50 -P 16 -t set,get -q
# -P 16 表示每个连接一次挂 16 条命令,QPS 通常能翻数倍
注意 Pipeline 不是原子的:中间可能插入其他客户端的命令;需要原子性要用 MULTI 或 Lua。
四、RESP 协议为什么省
请求:*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
↑数组长度 ↑参数长度 ↑参数 ↑参数长度 ↑参数
解析只需按 \r\n 切分 + 读长度,不需要递归下降、不需要 schema,
相比 JSON 省掉转义与类型推断,单条命令的解析成本常在百纳秒级。
协议的简单直接决定了"每字节的处理成本"很低,配合批量发送能让 CPU 有更多时间做真正的内存操作。代价是协议自身不表达类型信息,语义全靠命令与客户端封装。
五、常见追问
- 问:单线程为什么反而比多线程快?答:在"操作本身极短、竞争极多"的场景,多线程的锁与调度开销大于并行收益;Redis 的临界区只有几微秒,多线程拆分的收益覆盖不了协调成本。
- 问:那为什么 6.0 又要加 IO 多线程?答:瓶颈从 CPU 转移到了网络收发与协议解析,这部分可以并行且不碰数据结构,属于"安全的并行"。
- 问:Redis 慢的常见原因有哪些?答:大 key(单次响应几十 MB)、O(N) 命令(KEYS、HGETALL 大 hash)、集中过期、AOF always 刷盘、fork 时 COW 抖动、网络拥塞。
- 问:单实例 QPS 大概什么量级?答:普通 GET/SET 在 8~12 万 QPS;开了 Pipeline 可到几十万;具体取决于 value 大小与是否同机房。
- 问:既然内存这么快,为什么还要限制 maxmemory?答:内存是有限资源,无上限会触发 OOM Killer 或 swap,反而让延迟劣化到毫秒甚至秒级。
小结
- 快的根因是"内存 + 无锁 + 事件驱动 + 精简结构",单线程只是让这些条件更容易成立。
- 优化方向优先减少网络往返(Pipeline/批量/Lua),其次控制 value 体积,最后才考虑加机器。
- 面试时把"单线程"讲成权衡而非优点,才算答到点上。