Redis 通过使用操作系统提供的 IO 多路复用 机制来实现高性能的网络通信,使得单个线程能够同时处理多个客户端连接。以下是其核心实现方式:
1. 支持的 IO 多路复用模型
Redis 在运行时自动选择系统最高效的多路复用模型(按优先级):
- epoll(Linux)
- kqueue(FreeBSD/macOS)
- select(跨平台,但效率较低)
Redis 在 ae.c文件中抽象了事件驱动模型,通过条件编译适配不同平台。
2. 核心抽象:aeEventLoop
Redis 定义了事件循环结构体 aeEventLoop,其中关键字段包括:
- 事件处理器数组(用于存储文件事件)
- 就绪事件队列
- 底层多路复用 API 的函数指针(如
epoll_wait、kevent)
3. 事件处理流程
步骤 1:注册事件
- 客户端连接到 Redis 时,服务器会为对应的 socket 注册可读事件,并绑定回调函数(如
readQueryFromClient)。 - 当需要向客户端回复数据但 socket 暂时不可写时,注册可写事件,并绑定写回调函数。
步骤 2:监听就绪事件
- 调用
aeApiPoll(底层可能是epoll_wait/kevent/select)监听所有被注册的 socket。 - 当某个 socket 可读/可写时,多路复用器返回就绪事件列表。
步骤 3:处理事件
Redis 按顺序处理就绪事件,执行对应的读写回调函数。
4. 底层多路复用实现示例(以 epoll 为例)
Redis 在 Linux 下使用 epoll 的简化流程:
// 初始化 epoll
state->epfd = epoll_create(1024);
// 注册事件(如监听 socket 可读事件)
struct epoll_event ee;
ee.events = EPOLLIN; // 监听可读事件
ee.data.fd = fd;
epoll_ctl(state->epfd, EPOLL_CTL_ADD, fd, &ee);
// 事件循环
numevents = epoll_wait(state->epfd, events, AE_SETSIZE, timeout);
for (j = 0; j < numevents; j++) {
// 处理可读/可写事件
if (events[j].events & EPOLLIN) {
readProc(events[j].data.fd, ...);
}
if (events[j].events & EPOLLOUT) {
writeProc(events[j].data.fd, ...);
}
}
5. 为什么单线程还能高效?
- 非阻塞 IO:所有 socket 设置为非阻塞模式,读写不会阻塞线程。
- 事件驱动:只有活跃的 socket 会触发回调,避免了轮询所有连接的开销。
- 纯内存操作:数据操作在内存中完成,极快,不会阻塞网络 IO。
6. Redis 6.0 之后的优化
Redis 6.0 引入了多线程 IO(Threaded I/O),但仅用于处理网络数据的读写和解析,命令执行仍是单线程。这进一步提升了吞吐量,但核心的事件循环模型不变。
总结
Redis 的 IO 多路复用通过以下方式实现:
- 使用操作系统提供的
epoll/kqueue/select监听大量 socket。 - 事件驱动模型,只有活跃连接会触发回调。
- 单线程处理命令,避免了锁和上下文切换开销,同时通过非阻塞 IO 和事件循环实现高并发。
这种设计让 Redis 能以极低的延迟同时处理数万甚至数十万的并发连接。