从单线程到多线程 IO,深度解析 Redis 如何用极简设计支撑百万并发
Redis 处理命令的瓶颈从来不是 CPU,而是网络 IO。多线程引入锁竞争、上下文切换开销反而降低吞吐。单线程 + IO 多路复用 才是最优解。
Redis 的高性能 = 单线程命令执行 + epoll IO 多路复用 + 非阻塞网络
Redis 6.0+ 额外引入多线程专门处理网络读写,但命令执行仍是单线程。
基于 ae 事件库,封装 epoll/kqueue/select,统一处理 IO 与时间事件
所有客户端连接均设为非阻塞模式,读写不会挂起主线程
两种事件类型,文件事件处理网络 IO,时间事件处理定时任务
下方演示三种模型在处理多个客户端时线程的工作状态。绿色= 有效工作,灰色= 阻塞等待,红色= epoll 监听
Redis 主循环 aeMain() 不断执行一个 tick,每个 tick 分三步:
client connect() → server 监听 socket 触发 AE_READABLE 事件
调用 accept() 得到新 fd,设为非阻塞,注册读事件到 epoll
如 SET key value,数据到达内核缓冲区,对应 fd 变为可读
epoll 通知主线程:fd 可读,加入待处理队列
从 socket 读取字节流到 querybuf,调用 processInputBuffer() 解析 RESP 协议
在命令表 redisCommandTable 查找 SET,调用 setCommand() 写入内存数据库
执行结果 +OK\r\n 写入 client 的 buf 或 reply 链表
注册 AE_WRITABLE,下次事件循环调用 sendReplyToClient() 发送响应
响应发送完毕,删除 AE_WRITABLE 注册,等待下一个命令
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-threads 4(线程数建议为 CPU 核心数)+ io-threads-do-reads yes(开启读线程)。测试表明多线程 IO 可将 Redis 吞吐提升约 1.5 ~ 2x。
使用 POSIX select(),最多监听 1024 个 fd,足够早期场景
抽象 ae 事件库,Linux 用 epoll,macOS 用 kqueue,突破 fd 数量限制,支持数万并发连接
引入 BIO 后台线程处理耗时操作(UNLINK、FLUSHDB ASYNC),主线程仍同步处理 IO
正式引入多线程处理网络读写,命令执行保持单线程,性能大幅提升
引入 io_uring 实验性支持(部分版本),进一步降低系统调用开销