彻底搞懂 Redis 的事件驱动引擎 — 从数据结构到循环流程,从文件事件到时间事件
ae.c / ae.h)。它是 Redis 整个网络 IO 的核心引擎。
epoll,macOS 用 kqueue,其他用 selectstop 标志被置 1AE_READABLE / AE_WRITABLE / AE_BARRIER
epoll_wait 返回后填充,处理完当轮事件即"作废"。
aeMain() 就是一个 while(!stop) 循环,不断调用 aeProcessEvents()。每一次 aeProcessEvents() 叫做一个 Tick。
AE_READABLE fd 可读 → 有数据到来,或新连接到达
AE_WRITABLE fd 可写 → 输出缓冲区有空间,可以发送响应
AE_BARRIER 特殊标志:强制先写后读(AOF 持久化时用到)
/* 注册事件 */ int aeCreateFileEvent( aeEventLoop *el, int fd, int mask, // AE_READABLE | AE_WRITABLE aeFileProc *proc, void *clientData ); /* 注销事件 */ void aeDeleteFileEvent( aeEventLoop *el, int fd, int mask );
ae 在调用 epoll_wait 时不会无限阻塞,timeout 被设置为:
timeout = 最近时间事件触发时间 - 当前时间
这样既不会错过定时任务,又能最大化利用等待时间。
serverCron() — 每 100ms 触发一次
aeApiCreate / aeApiAddEvent / aeApiPoll)
/* ae.c 中的平台选择逻辑 */ #ifdef HAVE_EVPORT #include "ae_evport.c" // Solaris event ports #else #ifdef HAVE_EPOLL #include "ae_epoll.c" // Linux epoll ← 最常用 #else #ifdef HAVE_KQUEUE #include "ae_kqueue.c" // macOS/BSD kqueue #else #include "ae_select.c" // fallback,fd 上限 1024 #endif #endif #endif
推荐 生产环境首选,支持数十万并发连接
/* aeApiCreate:创建 epoll 实例 */ state->epfd = epoll_create(1024); // 参数已被忽略,随便写 /* aeApiAddEvent:注册 fd */ struct epoll_event ee = {0}; ee.events = EPOLLIN; // AE_READABLE → EPOLLIN ee.data.fd = fd; epoll_ctl(state->epfd, EPOLL_CTL_ADD, fd, &ee); /* aeApiPoll:等待就绪事件 */ int retval = epoll_wait(state->epfd, state->events, AE_SETSIZE, tvp ? tvp->tv_msec : -1);
macOS Redis 本地开发环境常用
/* aeApiCreate */ state->kqfd = kqueue(); /* aeApiAddEvent:注册事件 */ struct kevent ke; EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL); kevent(state->kqfd, &ke, 1, NULL, 0, NULL); /* aeApiPoll */ int retval = kevent(state->kqfd, NULL, 0, state->events, AE_SETSIZE, tvp);
不推荐生产 仅当系统无 epoll/kqueue 时使用
/* aeApiPoll */ fd_set rfds, wfds; FD_ZERO(&rfds); FD_ZERO(&wfds); // 遍历所有 fd,逐一加入 fd_set... select(maxfd+1, &rfds, &wfds, NULL, tvp);
stop == 0 → 继续;stop == 1 → Redis 关闭
刷新客户端输出缓冲区、处理 Cluster 等待回复、AOF 刷盘(如有)
遍历时间事件链表,找到最近触发时间 → timeout = max(0, 最近事件时间 - now)
主线程在此处阻塞,等待 fd 就绪或超时,内核把就绪 fd 填入 fired 数组
Redis 6.0+ 在此通知 IO 线程开始异步读取(多线程 IO 的入口)
对每个就绪 fd,若 mask & AE_READABLE → 调用 rfileProc(如 readQueryFromClient)
若 mask & AE_WRITABLE 且无 AE_BARRIER → 调用 wfileProc(如 sendReplyToClient)
若文件事件设了 AE_BARRIER 标志,强制先写后读(AOF fsync 时保证持久化顺序)
遍历时间事件链表,对 when ≤ now 的事件调用 timeProc 回调
返回 AE_NOMORE(-1) → 删除该事件;返回 retval ≥ 0 → when += retval,等待下次触发
id == AE_DELETED_EVENT_ID 的节点从链表中摘除并释放内存
整个过程在单线程中串行执行,无锁、无竞争,这就是 Redis 简单高效的秘密
events 数组,其下标是什么?epoll_wait 的超时时间由谁决定?-1(AE_NOMORE)意味着?AE_BARRIER 标志的作用是?