Redis 网络 IO 设计原理

从单线程到多线程 IO,深度解析 Redis 如何用极简设计支撑百万并发

⚡ 核心设计思路

🤔 为什么 Redis 不用多线程处理请求?

Redis 处理命令的瓶颈从来不是 CPU,而是网络 IO。多线程引入锁竞争、上下文切换开销反而降低吞吐。单线程 + IO 多路复用 才是最优解。

🎯 核心公式

Redis 的高性能 = 单线程命令执行 + epoll IO 多路复用 + 非阻塞网络

Redis 6.0+ 额外引入多线程专门处理网络读写,但命令执行仍是单线程。

🔄

事件驱动

基于 ae 事件库,封装 epoll/kqueue/select,统一处理 IO 与时间事件

📡

非阻塞 Socket

所有客户端连接均设为非阻塞模式,读写不会挂起主线程

🗂️

文件事件 + 时间事件

两种事件类型,文件事件处理网络 IO,时间事件处理定时任务

📊 IO 模型对比

阻塞 IO vs 非阻塞 IO vs IO 多路复用

下方演示三种模型在处理多个客户端时线程的工作状态。绿色= 有效工作,灰色= 阻塞等待,红色= epoll 监听

🐌 阻塞 IO(每连接一线程)

线程1 (C1)
等待数据
处理
等待
线程2 (C2)
等待数据
处理
线程3 (C3)
等待
处理
等待
线程4 (C4)
等待数据
处理
⚠️ N个客户端需要N个线程,大量线程在阻塞等待,内存与上下文切换开销极大

🚀 IO 多路复用(Redis 的方式)

epoll
监听 C1,C2,C3,C4...所有连接就绪事件
主线程
依次处理就绪事件 → 命令执行 → 回包
1个线程管理所有连接,CPU 始终在有效工作,零阻塞等待
🔁 事件循环 (aeMain) 动画

Redis 主循环 aeMain() 不断执行一个 tick,每个 tick 分三步:

Tick: 0
📨 完整请求处理流程
1
客户端发起 TCP 连接

client connect() → server 监听 socket 触发 AE_READABLE 事件

2
acceptHandler 接受连接

调用 accept() 得到新 fd,设为非阻塞,注册读事件到 epoll

3
客户端发送命令

SET key value,数据到达内核缓冲区,对应 fd 变为可读

4
epoll_wait 返回就绪事件

epoll 通知主线程:fd 可读,加入待处理队列

5
readQueryFromClient 读取数据

从 socket 读取字节流到 querybuf,调用 processInputBuffer() 解析 RESP 协议

6
命令查找与执行

在命令表 redisCommandTable 查找 SET,调用 setCommand() 写入内存数据库

7
addReply 写入输出缓冲区

执行结果 +OK\r\n 写入 client 的 bufreply 链表

8
注册写事件,回写响应

注册 AE_WRITABLE,下次事件循环调用 sendReplyToClient() 发送响应

9
回写完成,注销写事件

响应发送完毕,删除 AE_WRITABLE 注册,等待下一个命令

🔍 epoll 工作原理动画

epoll 是 Linux 内核提供的高效 IO 多路复用机制,Redis 在 Linux 下默认使用它。

三个核心系统调用

epoll_create() 创建 epoll 实例,返回 epfd


epoll_ctl(epfd, op, fd, event) 注册 / 修改 / 删除监听的 fd


epoll_wait(epfd, events, maxevents, timeout) 阻塞等待,返回就绪 fd 列表

红黑树 + 就绪链表

内核维护一棵红黑树存储所有监听的 fd,当 fd 就绪时,内核将其加入就绪链表


epoll_wait 只需遍历就绪链表,时间复杂度 O(就绪数),而非 O(总连接数)。这是 epoll 比 select/poll 快的根本原因。

🧵 Redis 6.0 多线程 IO

Redis 6.0 引入多线程处理网络读写,但命令执行仍是单线程,保证线程安全。

📥
Phase 1: 并行读取
多个 IO 线程并发从 socket 读取数据到各客户端 querybuf
IO Thread × N
⚙️
Phase 2: 串行执行
主线程单独顺序执行所有命令,操作内存数据库,零竞争
Main Thread × 1
📤
Phase 3: 并行写回
多个 IO 线程并发将响应数据写回各客户端 socket
IO Thread × N
💡 配置方式:io-threads 4(线程数建议为 CPU 核心数)+ io-threads-do-reads yes(开启读线程)。测试表明多线程 IO 可将 Redis 吞吐提升约 1.5 ~ 2x
📅 Redis 网络 IO 演进历史

Redis 1.0 — 单线程 + select

使用 POSIX select(),最多监听 1024 个 fd,足够早期场景

Redis 2.x — ae 事件库 + epoll/kqueue

抽象 ae 事件库,Linux 用 epoll,macOS 用 kqueue,突破 fd 数量限制,支持数万并发连接

Redis 4.0 — 部分异步操作

引入 BIO 后台线程处理耗时操作(UNLINKFLUSHDB ASYNC),主线程仍同步处理 IO

Redis 6.0 — 多线程网络 IO

正式引入多线程处理网络读写,命令执行保持单线程,性能大幅提升

Redis 7.x — 持续优化

引入 io_uring 实验性支持(部分版本),进一步降低系统调用开销