首页 > 编程学习 > 【Redis 源码学习笔记】Reactor模型 事件驱动框架

什么是 Reactor 模型

Reactor模型可以分为3种:
单线程Reactor模式
一个线程:
单线程:建立连接(Acceptor)、监听accept、read、write事件(Reactor)、处理事件(Handler)都只用一个单线程。

多线程Reactor模式
一个线程 + 一个线程池:
单线程:建立连接(Acceptor)和 监听accept、read、write事件(Reactor),复用一个线程。
工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包括数据就绪后,用户态的数据读写。

主从Reactor模式
三个线程池:
主线程池:建立连接(Acceptor),并且将accept事件注册到从线程池。
从线程池:监听accept、read、write事件(Reactor),包括等待数据就绪时,内核态的数据I读写。
工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包括数据就绪后,用户态的数据读写

Redis 如何实现 Reactor 模型

Redis 基于 Reactor 模型,实现了高性能的网络框架,通过事件驱动框架,Redis 可以使用一个循环来不断捕获、分发和处理客户端产生的网络连接、数据读写事件。

Redis 中实现事件驱动框架的三个关键函数:aeMain、aeProcessEvents,以及 aeCreateFileEvent。

在实现事件驱动框架的时候,你需要先实现一个主循环函数(对应 aeMain),负责一直运行框架。其次,你需要编写事件注册函数(对应 aeCreateFileEvent),用来注册监听的事件和事件对应的处理函数。

主循环 aeMain 函数


void aeMain(aeEventLoop *eventLoop) {eventLoop->stop = 0;while (!eventLoop->stop) {aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);}
}

事件注册:aeCreateFileEvent 函数

Linux 里提供了epoll_ctl API,Redis 在此基础上,封装了 aeApiAddEvent 函数,对 epoll_ctl 进行调用。

aeCreateFileEvent 就会调用 aeApiAddEvent,然后 aeApiAddEvent 再通过调用 epoll_ctl,来注册希望监听的事件和相应的处理函数。等到 aeProceeEvents 函数捕获到实际事件时,它就会调用注册的函数对事件进行处理了。

void initServer(void) {for (j = 0; j < server.ipfd_count; j++) {if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL) == AE_ERR){serverPanic("Unrecoverable error creating server.ipfd file event.");}}}

事件捕获与分发:aeProcessEvents 函数

aeProcessEvents 函数实现的主要功能,包括捕获事件、判断事件类型和调用具体的事件处理函数,从而实现事件的处理。


static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {//调用epoll_wait获取监听到的事件retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);if (retval > 0) {int j;//获得监听到的事件数量numevents = retval;//针对每一个事件,进行处理for (j = 0; j < numevents; j++) {#保存事件信息}}return numevents;
}

捕获事件函数:aeApiPoll 函数

Redis 是依赖于操作系统底层提供的 IO 多路复用机制,来实现事件捕获,检查是否有新的连接、读写事件发生。为了适配不同的操作系统,Redis 对不同操作系统实现的网络 IO 多路复用函数,都进行了统一的封装,封装后的代码分别通过以下四个文件中实现:

  • ae_epoll.c,对应 Linux 上的 IO 复用函数 epoll;
  • ae_evport.c,对应 Solaris 上的 IO 复用函数 evport;
  • ae_kqueue.c,对应 macOS 或 FreeBSD 上的 IO 复用函数 kqueue;
  • ae_select.c,对应 Linux(或 Windows)的 IO 复用函数 select。

Linux 上提供了 epoll_wait API,用于检测内核中发生的网络 IO 事件。在ae_epoll.c文件中,aeApiPoll 函数就是封装了对 epoll_wait 的调用。

这个封装程序如下所示,其中你可以看到,在 aeApiPoll 函数中直接调用了 epoll_wait 函数,并将 epoll 返回的事件信息保存起来的逻辑:


static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {//调用epoll_wait获取监听到的事件retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);if (retval > 0) {int j;//获得监听到的事件数量numevents = retval;//针对每一个事件,进行处理for (j = 0; j < numevents; j++) {#保存事件信息}}return numevents;
}

整个调用链工作:

在这里插入图片描述


本文链接:https://www.ngui.cc/article/show-564809.html
Copyright © 2010-2022 ngui.cc 版权所有 |关于我们| 联系方式| 豫B2-20100000