Redis 为什么是单线程的
因为 CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存或者网络带宽。(以上主要来自官方 FAQ)既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。但这个回答只解决了”为什么敢”,没解决”为什么快”,本文把这两层都拆开讲。
“单线程”到底指什么
先澄清概念:Redis 的单线程指的是网络 I/O 解析与命令执行由同一个主线程串行完成(accept 读取请求、解析命令、执行、写回响应都在一个线程里)。它并不是说整个进程只有一个线程——持久化的 fork、异步删除(unlink)、集群同步等都有后台线程参与。Redis 6.0 之后引入的”多线程”,也仅仅是把网络数据的读写和协议解析交给多个 I/O 线程,命令执行依然在主线程串行完成。
为什么单线程反而快
直觉上多线程才能榨干 CPU,但 Redis 的场景特殊:
- 纯内存操作:一次命令执行通常是微秒级的哈希表/跳表查找,CPU 计算量极小,真正的时间花在网络 I/O 和内存带宽上
- 多路复用:主线程用 epoll(IO multiplexing)同时监听上万连接,哪个连接可读就处理哪个,单线程也能撑起高并发连接
- 零锁开销:串行执行天然无竞态,不需要加锁、不需要处理死锁,也避免了多线程上下文切换和 cache line bouncing 的开销
- 数据结构加持:SDS、ziplist、skiplist 等结构都是为内存效率与常数级操作设计的
换句话说,在”每个请求只做一点点内存操作”的模型下,线程切换的成本可能比命令本身还贵,单线程是最经济的选择。
多线程会带来什么问题
假设把命令执行改成多线程:
- 所有数据结构都要考虑并发安全,加锁或 CAS 会显著增加复杂度与延迟
- 一条
LPUSH和一条LRANGE并发执行时需要保证原子性,否则用户语义被破坏 - 事务(MULTI/EXEC)、Lua 脚本的原子性保证将变得极其困难
Redis 选择把并发问题在架构层面消掉,而不是在每个操作里解决,这是典型的”以设计换复杂度”。
6.0 为什么又引入多线程
当单实例 QPS 上了几十万之后,瓶颈从命令执行转移到了网络字节读写与协议解析(系统调用和内存拷贝)。于是 6.0 把这部分拆给多个 I/O 线程,主线程只负责执行命令。官方数据是网络吞吐提升约一倍,而命令语义保持不变——这恰好印证了前面的判断:CPU 执行从来不是瓶颈,网络才是。
对使用的启示
理解单线程模型后,几条生产经验就自然成立了:
- 远离慢命令:
KEYS *、对大 key 的HGETALL/SMEMBERS会阻塞主线程,毫秒级的阻塞会让所有排队请求一起超时。曾用KEYS *排查问题的同事,让整集群 P99 抖了三秒 - 大 key 要拆:一个几百 MB 的 hash 不仅删除时卡主线程(用
UNLINK异步删),迁移时也会阻塞 - CPU 核数不必堆太多:单线程吃不满多核,单机 4-8 核足够,横向扩展靠主从与集群
- pipeline 提升吞吐:减少网络往返次数,比加线程更有效
单线程不是缺陷,而是 Redis 在”内存操作极快”这一前提下的最优解;6.0 的多线程也只是把真正变慢的网络 I/O 并行化。理解这一点,才能在使用时避开阻塞主线程的所有坑。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 非鱼小站!
评论
WalineDisqus











