一、Redis AE 事件库(IO 多路复用封装)
Redis 封装统一事件驱动库AE,兼容多种多路复用底层实现:epoll、kqueue、evport、select,对外提供一套统一 API:
aeApiCreate:创建多路复用实例(如 epoll_create)aeApiAddEvent:注册 fd + 事件(epoll_ctl)aeApiDelEvent:删除 fd 监听aeApiPoll:阻塞等待 fd 就绪(epoll_wait) 主循环入口:aeMain,循环调用aeProcessEvents处理就绪事件。
二、Redis 单线程网络模型流程(6.0 之前)
initServer():初始化服务,创建 server socket、注册 accept 连接处理器acceptTcpHandler、注册beforeSleep前置钩子aeMain启动事件循环,循环执行:- beforeSleep :遍历待写客户端队列
clients_pending_write,给需要返回结果的 fd 注册可写事件 ,绑定sendReplyToClient写回调 aeApiPoll:阻塞等待 fd 就绪- 遍历就绪 fd,执行对应 handler
- beforeSleep :遍历待写客户端队列
- 两个核心 handler:
acceptTcpHandler:收到新连接,拿到 client fd,绑定读处理器readQueryFromClientreadQueryFromClient: ① 读数据到queryBuf缓冲区 ②processInputBuffer解析缓冲区,拆成命令参数 ③processCommand执行 redis 命令 ④addReply:响应写入客户端缓存buf;buf 满则写入链表reply,加入clients_pending_write等待写事件回调返回给客户端
缓冲区说明:
queryBuf:接收客户端请求buf:优先存放响应reply链表:buf 容量不足时备用,无上限
三、Redis 是单线程还是多线程(高频面试题)
- v4.0 :主线程处理命令;新增后台异步线程 ,处理慢任务,如
unlink大 key 异步删除、flushdb 异步清空,命令执行依然单线程 - v6.0 :引入 IO 多线程,仅网络读写 / 解析命令、回包阶段多线程 ; ✅ 命令执行、事件循环(多路复用)仍然是主线程串行执行,保证命令串行、不用锁
一句话总结:命令执行是单线程;完整 redis 是多线程
为什么核心命令处理采用单线程?
- Redis 是内存数据库,瓶颈在网络 IO,不是 CPU 计算,多线程收益有限
- 规避多线程上下文切换开销
- 避免并发线程安全问题,不需要锁,实现简单、性能稳定
四、Linux 5 种 IO 模型对比(重点区分同步 / 异步)
判断标准:第二阶段(内核→用户空间拷贝数据)是否阻塞,阻塞 = 同步 IO;内核自动拷贝完成再通知 = 异步 IO | IO 模型 | 第一阶段(等数据就绪) | 第二阶段(拷贝到用户空间) | 同步 / 异步 | | ---- | ---- | ---- | ---- | | 阻塞 IO | 阻塞等待数据 | recvfrom 阻塞拷贝 | 同步 | | 非阻塞 IO | 用户轮询检查就绪 | recvfrom 阻塞拷贝 | 同步 | | IO 多路复用(epoll/select/poll) | 多路复用阻塞等待 fd 就绪 | recvfrom 阻塞拷贝 | 同步 | | 信号驱动 IO | 内核就绪发 SIGIO 信号通知 | recvfrom 阻塞拷贝 | 同步 | | 异步 IO (AIO) | 调用 aio_read 直接返回,不等待 | 内核自动完成拷贝,完成后发信号通知 | 异步 |
- 信号驱动 IO:预先注册 SIGIO 信号回调,数据就绪内核发信号通知;缺点:信号量大时容易队列溢出,信号交互开销高
- 异步 IO(AIO):全程非阻塞。调用 aio_read 直接返回,内核自己等数据、自己拷贝到用户缓冲区,全部做完才通知进程,唯一真正异步 IO 模型
- Redis 采用:IO 多路复用(epoll) + 事件驱动,属于同步 IO
五、Redis6.0 多线程模型补充
- 主线程:事件循环、accept 连接、执行 redis 命令
- IO 线程池:分担
readQueryFromClient读请求解析、sendReplyToClient回包写数据,充分利用多核,缓解网络 IO 压力 - 注意:命令执行依旧串行,不会并发执行命令,无竞争、不用锁