Redis 源码硬核解析系列专题 - 第五篇:事件驱动模型与网络层

第五篇:事件驱动模型与网络层

1. 引言

Redis的高性能很大程度上依赖其事件驱动模型和高效的网络层实现。基于单线程的事件循环,Redis能够处理大量并发连接而无需多线程开销。本篇将深入剖析Redis的事件循环框架(ae.c)和网络处理机制(networking.c),揭示其如何实现高并发。


2. 事件驱动模型概览

Redis的事件循环基于ae.c,支持两种事件:

  • 文件事件(File Event):处理客户端socket的读写。
  • 时间事件(Time Event):执行定时任务(如过期键清理)。

底层I/O多路复用机制根据系统选择:

  • Linux:epoll(默认)。
  • BSD/macOS:kqueue
  • Solaris:evport
  • 其他:select

3. 事件循环的核心结构

代码片段ae.h):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
typedef struct aeEventLoop {
    int maxfd;                  // 最大文件描述符
    aeFileEvent *events;        // 文件事件数组
    aeFiredEvent *fired;        // 已触发事件数组
    aeTimeEvent *timeEventHead; // 时间事件链表
    int stop;                   // 停止标志
    void *apidata;              // 多路复用API数据(如epoll)
} aeEventLoop;

typedef struct aeFileEvent {
    int mask;                   // 事件类型(AE_READABLE | AE_WRITABLE)
    aeFileProc *rfileProc;      // 读回调
    aeFileProc *wfileProc;      // 写回调
    void *clientData;           // 客户端数据
} aeFileEvent;

硬核解析

  • events:文件事件表,索引为fd。
  • fired:记录触发的事件。
  • timeEventHead:单链表存储定时任务。

Mermaid结构图

"events"

"fired"

"timeEventHead"

n
n
1

aeEventLoop

-maxfd: int

-events: aeFileEvent

-fired: aeFiredEvent

-timeEventHead: aeTimeEvent

-stop: int

-apidata: void

aeFileEvent

-mask: int

-rfileProc: aeFileProc

-wfileProc: aeFileProc

-clientData: void

aeFiredEvent

aeTimeEvent


4. 核心操作解析

4.1 创建文件事件(aeCreateFileEvent()

代码片段ae.c):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
                      aeFileProc *proc, void *clientData) {
    aeFileEvent *fe = &eventLoop->events[fd];
    if (aeApiAddEvent(eventLoop, fd, mask) == -1) return AE_ERR;
    fe->mask |= mask;
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData = clientData;
    if (fd > eventLoop->maxfd) eventLoop->maxfd = fd;
    return AE_OK;
}

硬核解析

  • aeApiAddEvent():调用底层API(如epoll_ctl)注册fd。
  • 事件类型AE_READABLE(可读)、AE_WRITABLE(可写)。
  • maxfd:便于动态调整事件表。
4.2 主事件循环(aeProcessEvents()

代码片段ae.c):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
    int processed = 0;
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;

    int numevents = aeApiPoll(eventLoop, eventLoop->fired); // I/O多路复用
    for (int j = 0; j < numevents; j++) {
        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
        int mask = eventLoop->fired[j].mask;
        if (mask & AE_READABLE) fe->rfileProc(eventLoop, fe->fd, fe->clientData, mask);
        if (mask & AE_WRITABLE) fe->wfileProc(eventLoop, fe->fd, fe->clientData, mask);
        processed++;
    }

    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop); // 处理时间事件
    return processed;
}

硬核解析

  • aeApiPoll():调用epoll_wait获取就绪事件。
  • 回调执行:根据mask调用读/写处理函数。
  • 时间事件:检查链表,执行到期任务。

Mermaid事件循环流程

Yes

No

Yes

No

aeProcessEvents()

aeApiPoll()

有文件事件?

执行rfileProc/wfileProc

有时间事件?

processTimeEvents()

返回processed

4.3 网络层:接受连接(acceptTcpHandler()

代码片段networking.c):

1
2
3
4
5
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
    if (cfd == ANET_ERR) return;
    createClient(cfd); // 创建客户端
}

硬核解析

  • anetTcpAccept():封装accept(),返回客户端fd。
  • createClient():初始化client结构体,注册读事件。

5. 单线程高并发的秘密

  • 非阻塞I/O:通过epoll避免轮询阻塞。
  • 事件分发:单线程按序处理,避免锁竞争。
  • 内存操作:无需线程同步,效率极高。

6. 总结与调试建议

  • 收获:理解Redis事件驱动与网络层实现。
  • 调试技巧:用strace跟踪epoll_wait调用,或用gdb打印eventLoop->fired
  • 下一步:探索内存管理和持久化。
updatedupdated2025-03-312025-03-31