经常会遇到这样一道问题: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...