Redis 单线程,为什么还这么快?

#中间件#面试八股

Redis 单线程,为什么还这么快?

结论:快不是因为"单线程"这个标签,而是四个条件同时成立:数据在内存里、访问路径上没有锁、IO 用多路复用不阻塞、数据结构与协议都足够省。单线程是结果而不是原因——正因为命令串行,才敢去掉所有并发控制,把每一次操作压到接近纯内存拷贝的开销。

一、性能账本:一次 GET 花在哪

环节典型耗时优化手段
网络往返(同机房)0.1~0.3 msPipeline、批量命令、就近部署
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 体积,最后才考虑加机器。
  • 面试时把"单线程"讲成权衡而非优点,才算答到点上。
笔记加载中…