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 執行鏈路就完整了。