Redis 事件驱动与多路复用源码剖析
1. 前言
Redis 是 单线程 + I/O 多路复用 的典型代表。
它并不是多线程处理请求,而是依赖 事件驱动(event-driven)模型,在一个线程内高效管理海量连接。
核心组件:
- ae.c:事件驱动框架,封装了事件循环(event loop)。
- anet.c:网络封装层,简化 socket API,提供可靠网络通信。
- I/O 多路复用:底层可使用 epoll(Linux)、kqueue(BSD)、select(通用)。
2. Redis 事件模型概览
2.1 两类事件
- 文件事件(File Event):指网络套接字上的读写操作。
- 时间事件(Time Event):定时器任务(如定期持久化、心跳、清理)。
2.2 事件循环
Redis 的事件循环大致流程:
while (server is running) {# 处理文件事件(网络 I/O)aeProcessEvents(loop);# 处理定时事件processTimeEvents();# 执行后台任务(AOF 重写、内存释放)cron();
}
👉 事件循环是 Redis 单线程并发调度的核心。
3. ae.c — 事件驱动框架
3.1 aeEventLoop 结构
在 ae.c
中,事件循环的核心结构是:
typedef struct aeEventLoop {int maxfd; // 当前已注册的最大 fdfd_set rfds, wfds; // 监听的读写事件aeFileEvent *events; // 已注册的文件事件aeFiredEvent *fired; // 已触发的文件事件aeTimeEvent *timeEventHead;// 时间事件链表int stop; // 是否停止循环
} aeEventLoop;
- events[]:保存所有已注册的 socket 事件。
- fired[]:保存已触发的事件,等待回调执行。
- timeEventHead:链表存放定时器任务。
3.2 注册文件事件
当一个客户端连接进来时,Redis 会调用 aeCreateFileEvent()
注册事件:
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,aeFileProc *proc, void *clientData) {aeFileEvent *fe = &eventLoop->events[fd];fe->mask |= mask; // 注册读/写事件fe->rfileProc = proc; // 设置事件处理回调
}
👉 每个 socket fd 都绑定了 读写事件回调函数。
3.3 事件分发与处理
事件循环执行时,会调用 aeProcessEvents()
:
int aeProcessEvents(aeEventLoop *eventLoop) {int numevents = aeApiPoll(eventLoop, ...); // 调用 epoll/select 等等待事件for (int j = 0; j < numevents; j++) {int fd = eventLoop->fired[j].fd;int mask = eventLoop->fired[j].mask;aeFileEvent *fe = &eventLoop->events[fd];if (mask & AE_READABLE) fe->rfileProc(fd, fe->clientData);if (mask & AE_WRITABLE) fe->wfileProc(fd, fe->clientData);}
}
👉 核心逻辑:等待事件 → 找到回调 → 执行回调。
4. anet.c — 网络通信层
anet.c
对 BSD socket API 做了封装,简化了网络调用。
4.1 建立连接
int anetTcpServer(char *err, int port, char *bindaddr) {int s = socket(AF_INET, SOCK_STREAM, 0);anetSetReuseAddr(s); // 设置 SO_REUSEADDRbind(s, ...);listen(s, 511); // backlog = 511return s;
}
👉 创建 TCP 服务器 socket,并监听端口。
4.2 接收新连接
int cfd = accept(s, ...);
anetNonBlock(cfd); // 设置非阻塞
anetEnableTcpNoDelay(cfd);
👉 新连接的 socket 设置为 非阻塞模式,避免 I/O 阻塞。
5. I/O 多路复用实现
Redis 在 ae_epoll.c / ae_kqueue.c / ae_select.c
中封装了多路复用实现。
5.1 epoll 示例
static int aeApiPoll(aeEventLoop *eventLoop, ...) {int numevents = epoll_wait(epfd, events, ...);for (int j = 0; j < numevents; j++) {eventLoop->fired[j].fd = events[j].data.fd;eventLoop->fired[j].mask = events[j].events;}return numevents;
}
👉 Linux 上默认使用 epoll,可支持 百万连接。
5.2 select 回退
如果系统不支持 epoll/kqueue,Redis 会回退到 select 实现,保证兼容性。
6. 源码调用链梳理
完整链路如下:
server.c (主循环)└─ aeMain()└─ aeProcessEvents()└─ aeApiPoll() # 调用 epoll_wait└─ rfileProc() # 读事件回调,读取客户端命令└─ wfileProc() # 写事件回调,返回响应
7. 单线程高并发的秘密
- 非阻塞 I/O:所有 socket 设置非阻塞。
- I/O 多路复用:同时监听大量 socket。
- 事件回调机制:读写操作通过回调函数执行,不会阻塞主线程。
- 计算与 I/O 解耦:命令解析、执行、响应都在主线程完成,避免线程切换开销。
👉 因此,Redis 在单线程下也能支撑 十万级并发请求。
8. 小结
本文解析了 Redis 事件驱动与多路复用的实现:
- ae.c:事件循环框架,统一调度文件事件和时间事件。
- anet.c:封装 socket API,提供简化的网络接口。
- I/O 多路复用:基于 epoll/kqueue/select,支持高并发连接。
- 单线程模型:通过事件驱动和非阻塞 I/O,避免多线程锁竞争。
📌 Redis 高性能的核心秘诀之一,就是 单线程 + 多路复用 的高效事件驱动架构。
👉 下一篇我们可以写 Redis 命令执行流程与内核数据结构(命令表、解析、执行、回复机制),这样整个 Redis 执行链路就完整了。