01.底层原理
文章目录
问题: Redis 是单线程的吗?请从网络模型、内存操作、数据结构设计等角度全面剖析其高性能的底层原因。
👇 点击展开/折叠 面试背诵模板(建议先背这个)
单线程 vs 多线程:到底怎么理解?
狭义(命令执行层面):Redis 处理客户端请求的获取(socket 读)、解析、执行、返回(socket 写)等核心流程,都由一个主线程顺序串行完成。
广义(整个 Redis 进程):Redis 内部一直有其他线程在运行,包括清理过期键值对、释放无用连接、内存淘汰策略执行、BGSAVE 生成 RDB 快照、AOF rewrite 等。所以 Redis 6.0 之前本质上是“单线程处理命令 + 多线程处理后台任务”的混合模型。
版本演进:
- v4.0:引入多线程处理耗时任务,如异步删除命令
unlink - v6.0:核心网络模型引入多 I/O 线程,将 socket 读写操作分配给多个线程并行处理,但命令执行仍由主线程完成
高性能的五大底层原因
① 纯内存操作
Redis 是基于内存的键值存储,每次读写操作本质上就是内存变量的读写。内存访问速度比磁盘快几个数量级,这是 Redis 快的基础。
② 高效的数据结构
Redis 针对不同数据类型设计了专属的底层结构:
- 动态字符串(SDS):替代 C 字符串,预分配内存减少重分配开销
- 跳跃表(Skip List):实现有序集合(ZSet),查询和插入均为 O(log N)
- 压缩列表(ZipList):连续内存存储,避免链表指针的内存消耗
- 字典(Dict):哈希表实现,O(1) 的读写性能
③ 单线程命令执行(避免锁与上下文切换)
单线程的优势在于:
- 无需考虑锁竞争、加锁解锁操作
- 不存在死锁风险
- 减少线程创建/销毁和上下文切换的 CPU 消耗
- 能够安全使用各种“线程不安全”的命令(如
LPUSH)
④ I/O 多路复用(核心网络模型)
Redis 基于 Reactor 模式开发了文件事件处理器(file event handler),这是单线程的,但通过 I/O 多路复用机制同时监听多个 socket。
工作流程:
- I/O 多路复用程序(Linux 下为 epoll)监听多个 socket
- 将产生事件的 socket 放入内存队列
- 事件分派器从队列中取出 socket,根据事件类型交给对应的事件处理器
这种设计使得单线程可以处理数万并发连接,因为线程不会被阻塞在某个慢速连接上。
⑤ 非阻塞 I/O
Redis 将所有客户端 socket 设为非阻塞模式。在非阻塞 I/O 下,read() 调用如果数据未就绪会立即返回 EAGAIN,而不是阻塞线程。配合 I/O 多路复用,只有在确定 socket 可读时才进行读取,最大化 CPU 利用率。
为什么 Redis 官方选择单线程(6.0 之前)?
官方给出的核心原因:
- Redis 的性能瓶颈主要在于内存和网络 I/O,CPU 不是瓶颈
- 普通 Linux 系统上 Redis 每秒可处理 100 万个请求
- 单线程模型代码可维护性高,实现简单
- 多线程会引入并发读写问题、线程切换、加锁解锁等复杂性和性能损耗
6.0 为何引入多 I/O 线程?
尽管单线程模型已经非常高效,但随着硬件发展,其局限性逐渐显现:
- CPU 利用率有限:现代服务器多为多核,单线程无法充分利用
- 网络 I/O 瓶颈:socket 的
read/write系统调用占用了大部分 CPU 时间,在高并发下成为性能瓶颈
6.0 的多 I/O 线程模型核心设计:
- I/O 线程仅负责网络读写和协议解析
- 命令执行仍由主线程完成(保持单线程的无锁优势)
- 主线程将请求分发给 I/O 线程并行读取,读取完成后主线程串行执行命令,最后再由 I/O 线程并行回写响应
这种“并行 I/O + 串行命令”的设计,既提升了网络吞吐量,又保留了单线程命令执行的简单性和一致性。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 LGQ的博客!