乐趣区

关于redis:Redis事件驱动框架

常常会遇到这样一道问题:Redis 的网络框架是实现了 Reactor 模型吗?

(1) Reactor 模型是什么

Reactor 模型是网络服务器端用来解决高并发网络 IO 申请的一种编程模型。

Reactor 的核心思想:Reactor 模式 也叫 Dispatcher 模式,将关注的 IO 事件注册到多路复用器上,一旦有 IO 事件触发,将事件散发到事件处理器中,执行就绪 IO 事件对应的处理函数中。

(1.1) 角色

Reactor 模型中定义的三种角色:

角色 职责
Reactor 负责监听和调配事件,将 I / O 事件分派给对应的 Handler。
Acceptor 解决客户端新连贯,并分派申请到处理器链中。
Handler 将本身与事件绑定,执行非阻塞读 / 写工作,实现 channel 的读入,实现解决业务逻辑后,负责将后果写出 channel。可用资源池来治理。

(1.2) 事件

Reactor 模型解决的是客户端和服务器端的交互过程,而这三类事件正好对应了客户端和服务器端交互过程中,不同类申请在服务器端引发的待处理事件:

事件类型 解释 备注
连贯事件 客户端和服务器建设连贯 对应 connect() 函数
写事件 连贯建设后,客户端会给服务器端发送读申请数据;\n 或 服务器端解决完客户端申请后写数据。 对应 write() 函数
读事件 服务器端读取申请数据。 对应 read() 函数

(1.3) Reactor 解决申请的流程

连贯事件由 acceptor 来解决,负责接管连贯;acceptor 在接管连贯后,会创立 handler,用于网络连接上对后续读写事件的解决;
读写事件由 handler 解决;
在高并发场景中,连贯事件、读写事件会同时产生,须要有一个角色专门监听和调配事件,这就是 reactor 角色。当有连贯申请时,reactor 将产生的连贯事件交由 acceptor 解决;当有读写申请时,reactor 将读写事件交由 handler 解决。

1、单 Reactor 单线程;
2、单 Reactor 多线程;
3、主从 Reactor 多线程。

(2) 为什么要用 Reactor 模型

(2.1) BIO 模型的优缺点

长处:
1、应用简略,容易编程
2、在多核零碎下,可能充分利用了多核 CPU 的资源。

毛病:
该模式的实质问题在于重大依赖线程,随着并发访问量的减少,线程数量的一直收缩将服务端的性能将急剧下降。
1、线程的创立与销毁都须要调用零碎函数,开销比拟高。
2、资源耗费大。大量闲暇的线程会占用许多内存;并发量大时,线程资源争抢重大,CPU 性能可能会降落。
3、线程的切换老本高。操作系统产生线程切换的时候,须要保留线程的上下文,而后执行零碎调用。线程数过高,会带来许多无用的上下文切换,可能导致执行线程切换的工夫甚至会大于线程执行的工夫,这时候带来的体现往往是零碎负载偏高、CPU sy(零碎 CPU) 使用率特地高。
4、客户端和服务器端的连贯会始终保留着,可能会存在大量线程在大量工夫内都处于空置状态的状况。

(2.2) Reactor 模型的优缺点

Reactor 模型具备如下的长处:

响应快,不用为单个同步工夫所阻塞,尽管 Reactor 自身仍然是同步的;
编程绝对简略,能够最大水平的防止简单的多线程及同步问题,并且防止了多线程 / 过程的切换开销;
可扩展性,能够不便地通过减少 Reactor 实例个数来充分利用 CPU 资源;
可复用性,Reactor 模型自身与具体事件处理逻辑无关,具备很高的复用性。

(3) 如何依照 Reactor 模型实现的

Redis 为了实现事件驱动框架,相应地定义了事件的数据结构、框架主循环函数、事件捕捉散发函数、事件和 handler 注册函数。

(4) 源码解析

(4.1) 事件的数据结构

Redis 的事件驱动框架定义了两类事件:IO 事件和工夫事件,别离对应了客户端发送的网络申请和 Redis 本身的周期性操作。

// file: ae.h 

/** 文件事件构造 */
typedef struct aeFileEvent {
    int mask;  // 标记 可读 / 可写 / 屏障
    aeFileProc *rfileProc;  // 写事件函数
    aeFileProc *wfileProc;  // 读事件函数
    void *clientData;  // 
} aeFileEvent;
// file: ae.h 

/* 工夫事件构造 */
typedef struct aeTimeEvent {
    long long id; /* time event identifier. */
    long when_sec; /* seconds */
    long when_ms; /* milliseconds */
    aeTimeProc *timeProc;
    aeEventFinalizerProc *finalizerProc;
    void *clientData;
    struct aeTimeEvent *prev;
    struct aeTimeEvent *next;
    int refcount; /* refcount to prevent timer events from being
             * freed in recursive time event calls. */
} aeTimeEvent;

(4.2) 事件执行入口 -main 函数

// file: src/ae.c 

/**
 * 循环处理事件
 * 
 * @param *eventLoop 
 */ 
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    // 循环处理事件
    while (!eventLoop->stop) {
        // 处理事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|
                                   AE_CALL_BEFORE_SLEEP|
                                   AE_CALL_AFTER_SLEEP);
    }
}

(4.3) 获取就绪事件并散发

// file: src/ae.c 

/**
 * 处理事件
 * 
 * @param *eventLoop
 * @param flags
 */ 
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    // 省略局部细节...

    // 调用多路复用 API 获取就绪事件
    numevents = aeApiPoll(eventLoop, tvp);

    // 解决写事件
    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
    // 解决读事件
    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                        
}
// file: src/ae_poll.c 

/**
 * 获取就绪事件
 * 
 * @param *eventLoop
 * @param *tvp
 */ 
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    // 期待事件
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    // 调 linux epoll_wait 函数来获取已就绪 socket
    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

    // ...

    return numevents;
}

(4.4) 事件注册

// file:  src/ae.c

/**
 * @param *eventLoop
 * @param fd 
 * @param mask 0: 未注册事件  1: 描述符可读时触发  2: 描述符可写时触发  3:
 * @param *proc aeFileProc 类型  入参传的是 acceptTcpHandler 函数 回调时会用到这个函数 
 * @param *clientData
 */ 
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }

    // 从 aeFileEvent 事件数组里取出一个文件事件构造
    aeFileEvent *fe = &eventLoop->events[fd];

    // 监听指定 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;
}

Redis 高性能 IO 模型 https://weikeqin.com/2022/01/…

Redis 源码分析与实战 学习笔记 Day10 10 Redis 事件驱动框架(中):Redis 实现了 Reactor 模型吗?
https://time.geekbang.org/col…

退出移动版