Reactor 模式

2023-05-16

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 进行处理,不会再分配给其他子进程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Reactor 模式 的相关文章

  • reactor和proactor模式

    首先就第一篇 Reactor模式 xff0c 或者叫反应器模式 做一下笔记 xff1a 刚开店做生意 xff0c 老板为了给顾客一个美好的印象 xff0c 给顾客最好的服务 xff0c 一对一 随着经营的生意越来越好 xff0c 顾客多了
  • Reactor 模式

    Reactor 翻译过来的意思是 反应堆 xff0c 可能大家会联想到物理学里的核反应堆 xff0c 实际上并不是的这个意思 这里的反应指的是 对事件反应 xff0c 也就是来了一个事件 xff0c Reactor 就有相对应的反应 响应
  • 从io模型到ppc,tpc,reactor,preactor

    所有的系统I O都分为两个阶段 xff1a 等待就绪和操作 读就是等待系统可读和真正的读 写就是等待系统可写和真正的写 1 网络io模型 这是我们常见的一张图 1 传统的bio 就是同步阻塞的 当调用socket read的时候 会阻塞 直
  • reactor/proactor模型简介

    Reactor和preactor都是IO多路复用模式 xff0c 一般地 I O多路复用机制都依赖于一个事件多路分离器 Event Demultiplexer 分离器对象可将来自事件源的I O事件分离出来 xff0c 并分发到对应的read
  • Reactor模型

    前言 首先让我们来回顾一下select poll和epoll是如何获取网络事件的 xff1a 在获取事件时 xff0c 先把我们要关心的连接传给内核 xff0c 再由内核检测 xff1a 若没有事件发生 xff0c 线程只需阻塞在这个系统调
  • epoll高度封装reactor,几乎所有可见服务器的底层框架

    目录 前言 reactor是什么 如何理解 reactor所需组件流程分析 组件 流程 如何将epoll的IO驱动封装成reactor事件反应堆驱动 reactor分块分析实现 注册事件处理器部分流程 多路复用器监视多路IO事件 事件分发器
  • Java中的NIO和IO的对比分析

    总的来说 java中的IO和NIO主要有三点区别 IO NIO 面向流 面向缓冲 阻塞IO 非阻塞IO 无 选择器 Selectors 1 面向流与面向缓冲 Java NIO和IO之间第一个最大的区别是 IO是面向流的 NIO是面向缓冲区的
  • Spring WebClient - 如何处理错误场景

    我们正在使用org springframework web reactive function client WebClient with reactor netty http client HttpClient作为 Spring 5 1
  • Spring Reactor 合并与 Concat

    我正在玩 Spring Reactor 我看不出两者之间有什么区别concat and merge操作员 这是我的例子 Test public void merge Flux
  • 如何使用项目反应器实现调用重复,直到满足特定条件?

    有没有什么方法可以使用项目反应器来做这样的事情 fetchSystemUpdates return Mono
  • 如何在reactor-kafka中重试失败的ConsumerRecord

    我正在尝试使用reactor kafka来消费消息 其他一切都工作正常 但我想为失败的消息添加重试 2 spring kafka默认已经重试失败记录3次 我想使用reactor kafka实现相同的效果 我使用 spring kafka 作
  • 项目反应器:collectList()不适用于Flux.create()

    下面的示例打印从 1 到 10 的整数以及 7 8 9 10 的列表 public void streamCollect ConnectableFlux
  • Akka 或 Reactor [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我正在启动一个新项目 基于java 我需要将其构建为模块化 分布式和弹性架构 因此 我希望业务流程能够相互通信 互操作 但又独立 我现在正在研究
  • Mono 和 Mono.empty() 有何不同

    据我了解 在 Spring WebFlux 反应器中 Mono
  • 如何使用Reactor框架2.x执行多线程map/reduce?

    我之前问过这个问题 https stackoverflow com questions 22844717 how do you execute map reduce operations with the reactor framework
  • 对于 Node.js 中的本地文件,我是否应该使用异步文件 IO 方法而不是同步方法?

    我有一个非常简单的实用程序脚本 是用 JavaScript 为 node js 编写的 它读取文件 进行一些计算 然后写入输出文件 当前形式的源代码看起来像这样 fs readFile inputPath function err data
  • 如何在 Spring 5 WebFlux WebClient 中设置超时

    我正在尝试在 WebClient 上设置超时 这是当前的代码 SslContext sslContext SslContextBuilder forClient trustManager InsecureTrustManagerFactor
  • 从 InputStream 获取 Publisher

    我刚刚升级了 mongodb java driver 现在有了方便的功能GridFSBucket uploadFromStream走了 因此我们现在得到了一个 GridFSUploadPublisher
  • Spring webflux 中 Mono> 与 Flux 的区别

    我的理解是Mono
  • Scrapy - 使用 TwistedScheduler 时出现 ReactorAlreadyInstalledError

    我有以下 Python 代码来启动 APScheduler TwistedScheduler cronjob 来启动蜘蛛 使用一只蜘蛛不是问题 而且效果很好 然而 使用两个蜘蛛会导致错误 twisted internet error Rea

随机推荐

  • OSPF(二)DR与BDR选举

    文章目录 MA网络中的问题DR amp DBRDR与BDR的选举 MA网络中的问题 在运行OSPF的MA网络包括广播型网络和NBMA网络 xff0c 都会存在两个问题 在一个有n个路由器的网络 xff0c 会形成 n n 1 2个邻接关系
  • 防火墙详解(三)华为防火墙基础安全策略配置(命令行配置)

    实验要求 根据实验要求配置防火墙 xff1a 合理部署防火墙安全策略以及安全区域实现内网用户可以访问外网用户 xff0c 反之不能访问内网用户和外网用户均可以访问公司服务器 实验配置 步骤一 xff1a 配置各个终端 防火墙端口IP地址 终
  • 常见信息安全威胁与经典案例

    文章目录 信息安全威胁现状网络战争的开端 xff1a 震网 病毒信息安全攻击事件的演变安全威胁分类 网络安全威胁案例美国Dyn DNS服务遭受DDoS攻击Mirai病毒发动攻击过程 xff08 DDos攻击过程 xff09 扫描获取控制权限
  • 华为FusionCompute详解(一)FusionSphere虚拟化套件介绍

    文章目录 虚拟化FusionSphere虚拟化套件特点管理简单性能至优 FusionSphere虚拟化套件组成FusionSphere服务器虚拟化架构FusionSphere架构特点与用途FusionSphere应用场景单虚拟化场景多虚拟化
  • 华为FusionCompute详解(二)FusionCompute总体介绍以及规划部署

    文章目录 FusionCompute产品定位FusionCompute产品架构FusionCompute软件逻辑组成 FusionCompute产品功能虚拟化计算虚拟化存储虚拟化网络 FusionCompute带来的价值FusionComp
  • Docker基础篇(1)

    文章目录 Docker简介是什么概念容器VS虚拟机能干什么技术职级变化一次构建 xff08 镜像 xff09 xff0c 到处运行 xff1a 去哪下 Docker简介 是什么 为什么会有Docker出现 xff1a 解决重复的环境部署开销
  • openStack:学习openStack的前提知识(1)虚拟化以及KVM简介

    文章目录 KVM简介KVM核心组件Libvirt组件 KVM简介 KVM 目前X86平台上最热门 xff0c 运用最多的虚拟化解决方案 xff0c openStack对KVM支持也是最好的 所以后续使用KVM作为Hypervisor xff
  • 解决WSL2/ubuntu安装软件报错 E: Invalid operation insatll 问题

    文章目录 解决WSL2安装软件报错E Invalid operation insatll 问题方法一方法二 解决WSL2安装软件报错E Invalid operation insatll 问题 此问题为系统定位不到软件包 xff0c 无法安
  • github-copilot的使用步骤

    1 首先登录你的github账号 xff0c 点击头像 xff0c 点击设置 2 在设置界面点击Copilot 并点击try Colilot 3 点击绿色按钮继续 4 设置为允许 xff0c 并取消允许使用你的代码来改进这一选项 xff0c
  • Arduino IDE配置esp8266开发环境

    目录 前言arduino IDE安装配置ESP8266开发环境通过开发板管理器安装手动安装开发版管理器备用的网址 参考 前言 之前本科那会疫情在家用esp8266 ESP 01s Relay模块做了一个远程浇花的设备 xff0c 当时使用a
  • 三、Ubuntu 18.04系统调试(命令/换源)

    目录 一 常用命令 二 Ubuntu 18 04换源 2 1便捷方法 2 2命令行方法 xff08 较为复杂 xff0c 但可查看防止后期有些错误是因为源导致的源文件 xff09 一 常用命令 目录操作 pwd 查看当前目录 cd 返回上一
  • 使用VScode远程操作虚拟机(ubuntu)

    1 VSCode安装 2 打开Ubuntu 使用ifconfig 获取系统接口 3 打开remote ssh 4 配置好相关属性 5 开启远程连接输入密码即可连接
  • 学习率(Learing Rate)的作用以及如何调整

    1 什么是学习率 学习率 Learning rate 作为监督学习以及深度学习中重要的超参 xff0c 其决定着目标函数能否收敛到局部最小值以及何时收敛到最小值 合适的学习率能够使目标函数在合适的时间内收敛到局部最小值 这里以梯度下降为例
  • Pytorch 中net.train() 和 net.eval()的作用和如何使用?

    一般在训练模型的代码段加入 xff1a model train 在测试模型时候加入 xff1a model eval 同时发现 xff0c 如果不写这两个程序也可以运行 xff0c 这是因为这两个方法是针对在网络训练和测试时采用不同方式的情
  • Qt 子窗口内嵌到父窗口中

    有时需要把一个子窗口内嵌进入父窗口当中 我们可以这样做 1 新建一个QWidget 或者QDialog的子类 ClassA xff08 父类为ClassB xff09 2 在新建类的构造函数中添加设置窗口属性 setWindowFlags
  • 用Cmake 编译OpenCV常见的错误

    minGW32 make遇到的错误1 xff1a 37 Linking CXX shared library bin libopencv core341 dll CMakeFiles opencv core dir objects a me
  • 卷积 反卷积 上采样 下采样 区别

    1 卷积 就是利用卷积核 步长前进 卷积整个图片 2 反卷积 反卷积的具体操作 原图输入尺寸为 1 xff0c 3 xff0c 3 xff0c 3 对应 batch size channels width height 反卷积tconv 6
  • Go语言操作数据库MySQL

    连接 Go语言中的database sql包提供了保证SQL或类SQL数据库的泛用接口 xff0c 并不提供具体的数据库驱动 使用database sql包时必须注入 xff08 至少 xff09 一个数据库驱动 我们常用的数据库基本上都有
  • 解决Git请求错误问题

    git clone gits github com Cloning into 39 FdogSerialize 39 git 39 remote gits 39 is not a git command See 39 git help 39
  • Reactor 模式

    Reactor 翻译过来的意思是 反应堆 xff0c 可能大家会联想到物理学里的核反应堆 xff0c 实际上并不是的这个意思 这里的反应指的是 对事件反应 xff0c 也就是来了一个事件 xff0c Reactor 就有相对应的反应 响应