Reactor 模式

news/2024/4/17 16:28:31

Reactor 翻译过来的意思是「反应堆」,可能大家会联想到物理学里的核反应堆,实际上并不是的这个意思。

这里的反应指的是「对事件反应」,也就是来了一个事件,Reactor 就有相对应的反应/响应

事实上,Reactor 模式也叫 Dispatcher 模式,我觉得这个名字更贴合该模式的含义,即 I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程

Reactor 模式主要由 Reactor 和处理资源池这两个核心部分组成,它俩负责的事情如下:

  • Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
  • 处理资源池负责处理事件,如 read -> 业务逻辑 -> send;

Reactor 模式是灵活多变的,可以应对不同的业务场景,灵活在于:

  • Reactor 的数量可以只有一个,也可以有多个;
  • 处理资源池可以是单个进程 / 线程,也可以是多个进程 /线程;

将上面的两个因素排列组设一下,理论上就可以有 4 种方案选择:

  • 单 Reactor 单进程 / 线程;
  • 单 Reactor 多进程 / 线程;
  • 多 Reactor 单进程 / 线程;
  • 多 Reactor 多进程 / 线程;

其中,「多 Reactor 单进程 / 线程」实现方案相比「单 Reactor 单进程 / 线程」方案,不仅复杂而且也没有性能优势,因此实际中并没有应用。

剩下的 3 个方案都是比较经典的,且都有应用在实际的项目中:

  • 单 Reactor 单进程 / 线程;
  • 单 Reactor 多线程 / 进程;
  • 多 Reactor 多进程 / 线程;

方案具体使用进程还是线程,要看使用的编程语言以及平台有关:

  • Java 语言一般使用线程,比如 Netty;
  • C 语言使用进程和线程都可以,例如 Nginx 使用的是进程,Memcache 使用的是线程。

接下来,分别介绍这三个经典的 Reactor 方案。

Reactor

单 Reactor 单进程 / 线程

一般来说,C 语言实现的是「单 Reactor 单进程」的方案,因为 C 语编写完的程序,运行后就是一个独立的进程,不需要在进程中再创建线程。

而 Java 语言实现的是「单 Reactor 单线程」的方案,因为 Java 程序是跑在 Java 虚拟机这个进程上面的,虚拟机中有很多线程,我们写的 Java 程序只是其中的一个线程而已。

我们来看看「单 Reactor 单进程」的方案示意图:

 

可以看到进程里有 Reactor、Acceptor、Handler 这三个对象:

  • Reactor 对象的作用是监听和分发事件;
  • Acceptor 对象的作用是获取连接;
  • Handler 对象的作用是处理业务;

对象里的 select、accept、read、send 是系统调用函数,dispatch 和 「业务处理」是需要完成的操作,其中 dispatch 是分发事件操作。

接下来,介绍下「单 Reactor 单进程」这个方案:

  • Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
  • 如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
  • 如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;
  • Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。

单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。

但是,这种方案存在 2 个缺点:

  • 第一个缺点,因为只有一个进程,无法充分利用 多核 CPU 的性能
  • 第二个缺点,Handler 对象在业务处理时,整个进程是无法处理其他连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟

所以,单 Reactor 单进程的方案不适用计算机密集型的场景,只适用于业务处理非常快速的场景

Redis 是由 C 语言实现的,它采用的正是「单 Reactor 单进程」的方案,因为 Redis 业务处理主要是在内存中完成,操作的速度是很快的,性能瓶颈不在 CPU 上,所以 Redis 对于命令的处理是单进程的方案。

单 Reactor 多线程 / 多进程

如果要克服「单 Reactor 单线程 / 进程」方案的缺点,那么就需要引入多线程 / 多进程,这样就产生了单 Reactor 多线程 / 多进程的方案。

闻其名不如看其图,先来看看「单 Reactor 多线程」方案的示意图如下:

 

详细说一下这个方案:

  • Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
  • 如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
  • 如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;

上面的三个步骤和单 Reactor 单线程方案是一样的,接下来的步骤就开始不一样了:

  • Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;
  • 子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;

单 Reator 多线程的方案优势在于能够充分利用多核 CPU 的能,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。

例如,子线程完成业务处理后,要把结果传递给主线程的 Reactor 进行发送,这里涉及共享数据的竞争。

要避免多线程由于竞争共享资源而导致数据错乱的问题,就需要在操作共享资源前加上互斥锁,以保证任意时间里只有一个线程在操作共享资源,待该线程操作完释放互斥锁后,其他线程才有机会操作共享数据。

聊完单 Reactor 多线程的方案,接着来看看单 Reactor 多进程的方案。

事实上,单 Reactor 多进程相比单 Reactor 多线程实现起来很麻烦,主要因为要考虑子进程 <-> 父进程的双向通信,并且父进程还得知道子进程要将数据发送给哪个客户端。

而多线程间可以共享数据,虽然要额外考虑并发问题,但是这远比进程间通信的复杂度低得多,因此实际应用中也看不到单 Reactor 多进程的模式。

另外,「单 Reactor」的模式还有个问题,因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方

多 Reactor 多进程 / 线程

要解决「单 Reactor」的问题,就是将「单 Reactor」实现成「多 Reactor」,这样就产生了第 多 Reactor 多进程 / 线程的方案。

老规矩,闻其名不如看其图。多 Reactor 多进程 / 线程方案的示意图如下(以线程为例):

 

方案详细说明如下:

  • 主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
  • 子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。
  • 如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。
  • Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。

多 Reactor 多线程的方案虽然看起来复杂的,但是实际实现时比单 Reactor 多线程的方案要简单的多,原因如下:

  • 主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理。
  • 主线程和子线程的交互很简单,主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端。

大名鼎鼎的两个开源软件 Netty 和 Memcache 都采用了「多 Reactor 多线程」的方案。

采用了「多 Reactor 多进程」方案的开源软件是 Nginx,不过方案与标准的多 Reactor 多进程有些差异。

具体差异表现在主进程中仅仅用来初始化 socket,并没有创建 mainReactor 来 accept 连接,而是由子进程的 Reactor 来 accept 连接,通过锁来控制一次只有一个子进程进行 accept(防止出现惊群现象),子进程 accept 新连接后就放到自己的 Reactor 进行处理,不会再分配给其他子进程。


https://www.xjx100.cn/news/663766.html

相关文章

设计模式 - Reactor 模式

https://blog.csdn.net/saienenen/article/details/111400911 1. Reactor模式简介 Netty是典型的Reactor模型结构。Reactor模式也叫反应器模式&#xff0c;大多数IO相关组件如Netty、Redis在使用的IO模式。 2. 多线程 IO 的致命缺陷 最最原始的网络编程思路就是服务器用一个…

Java IO篇:什么是 Reactor 网络模型?

一、什么是 Reactor 模型&#xff1a; The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and di…

佳能2545i打印机驱动安装问题

记录一个打印机驱动安装的问题&#xff0c;起初在佳能售后官网下载了第一个驱动Generic PCL6 -2.86这个版本&#xff0c;但是装上之后没有效果&#xff0c;就很奇怪&#xff0c;然后询问了同事说是下载UFRII LT这个版本的就可以&#xff0c;最终成功打印&#xff0c;由衷的的感…

C++ QT读写Microsoft Word文档基操

以下是一个简单的示例&#xff0c;演示如何使用C QT读写Microsoft Word文档&#xff1a; #include <QAxObject> #include <QDebug> void readWordDocument(QString filePath) { QAxObject* word new QAxObject("Word.Application", 0);…

如何高质量产出与进步

个人困惑 明明有很多时间&#xff0c;却感觉没有事做。明明有很多事做&#xff0c;却不知道从哪里做起。明明知道某个事很重要&#xff0c;却一直无法开始。眼前有好几件事&#xff0c;却无法推进。本来想好要做的事&#xff0c;中途坚持不下去了。 背后根因思考 直接原因是…

VTK学习之光照和相机

目录 一、VTK光照 1、关于vtkLight常用的方法 2、最终效果 二、相机设置 1、相机设置 2、效果 一、VTK光照 通过设置光照&#xff0c;可以达到不同颜色的目的&#xff0c;参考博客&#xff1a; VTK修炼之道7_三维场景基本要素:光照_vtk 光照_沈子恒的博客-CSDN博客 1…

珂朵莉树ODT(基于std::set的暴力玄学数据结构)

使一整段区间内的东西变得一样&#xff0c;数据随机。 在具有区间赋值操作&#xff0c;区间统计操作&#xff0c;以及最好保证数据随机的情况下在时空复杂度上把线段树吊起来打。 珂朵莉树的各种操作的总体复杂度始终为O(NlogN)&#xff0c;这会吊打某些常数大、附加工作会影…

DDR中的ODT

ODT电阻端接 ODT (on-die termination) 裸片终端&#xff08;ODT&#xff09;功能允许DRAM通过ODT控制引脚为x4 / x8配置的每个DQ&#xff0c;DQS / DQS&#xff0c;RDQS / RDQS和DM信号打开/关闭终端电阻。对于x16配置&#xff0c;ODT通过ODT控制引脚应用于每个DQ&#xff0…