0、引言

在上一篇的笔记中,我们学习了操作系统提供的高效I/O管理技术,主要用于解决服务器在高并发场景下的资源浪费和瓶颈问题。但是在实际的代码编写中,要是我们都全部调用底层的I/O多路复用接口来编写网络程序这种面向过程的方式,必然会导致开发的效率不高。于是在这一章节,我们来学习两个重要的Reactor和Proactor模型,他们都借用了I/O多路复用机制来高效地管理和分发事件,并且更利于程序员进行程序开发的代码编写。

1、Reactor和Proactor

基于面向对象的思想,大佬们对I/O多路复用作了一层封装,让使用者不用考虑底层网络接口的细节,只需要关注应用代码的编写。

大佬们为这个模式取名为:Reactor模式,翻译过来的意思为「反应堆」,这个反应堆指的是「对事件反应」,也就是说来一个事件,Reactor就有相对应的反应/响应。

Reactor模式也叫Dispatcher模式,即I/O多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程/线程。

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

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

(说到这里,我觉得就像是使用go的gin框架编写web程序时,需要在router配置路由一样,这就对应了Reactor的监听事件,但是具体的处理流程就交给handler来处理一样,这种思想具有相似性正是因为这种设计思想在面对复杂逻辑的时候具有更高效的并发处理效率。)

Reactor模式是灵活多变的,在理论上该模式有4种方案的选择:

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

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

省下的3个方案都是比较经典主流的。

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

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

2、Reactor

2.1、单Reactor单进程/线程

下面是一张「单Reactor单进程」的方案示意图

进程中有Reactor、Acceptor、Handler三个对象:

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

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

该方案的流程如下:

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

该方案有两个缺点:

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

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

Redis是由C语言实现的,在6.0版本之间采用的就是这种方案。

2.2、单Reactor多进程/线程

为了弥补单进程/线程的缺点,于是引入了多进程/线程。

我们来看它的不同之处:

  • Handler不再去处理具体的业务逻辑,而是只负责数据的接收和发送。
  • 具体的业务逻辑通过分配一个字线程Processor去完成,将处理结果返回给Handler对象。

这种方案的优势在于能够充分利用多核CPU的性能,但是引入了多线程就自然带来了线程竞争资源的问题。

我们需要使用互斥锁来保证对共享资源的并发访问问题。

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

2.3、多Reactor多进程/线程

为了能够面对瞬间高并发的场景,于是进而引用多Reactor。

它的方案大致步骤是:主线程的MainReactor对象通过select监控连接建立事件,收到事件后通过Acceptor对象获取连接,此时子Reactor会将MainReactor建立的连接加入进select继续监听,在今后的事件发生中,转而调用SubReactor对应的Handler对象来响应。

这种方案实现起来更加的简单:

  • 主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理。
  • 主线程和子线程的交互简单,只需要把连接传递给子线程,无需等待返回数据。

3、Proactor

Reactor是非阻塞同步网络模式,而Proactor是异步网络模式

在非阻塞I/O中,read请求在数据未准备完善的时候立刻返回,可以继续往下执行,此时进程不断地轮询内核,直到数据准备好后,内核将数据拷贝到用户缓存区,read调用才可以获取到结果,过程如下:

需要注意的是,这里的最后一次read调用中,等待数据从内核缓冲区拷贝到用户缓冲区的过程是需要等待的,也就是说这个步骤是「同步的」。

因此,无论read和send是阻塞I/O还是非阻塞I/O,都是同步调用。如果内核实现的拷贝效率不高,read调用就会在这个同步过程中等待比较长的时间。

而真正的异步I/O是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程都不需要等待。

当我们发起aio_read(异步I/O)之后,就立即返回,内核自动将数据从内核空间拷贝到用户空间,这个拷贝过程同样是异步的,应用程序并不需要主动发起拷贝动作

显而易见的是,异步I/O比同步I/O的性能更好。

Proatcor正是采用了异步I/O技术,所以被称为异步网络模型。

我们再来对比一下Reactor和Proactor的区别:

  • Reactor是非阻塞同步网络模式,感知的是就绪可读写事件。在每次感知到有事件发生的时候,就需要应用程序主动调用read方法来完成数据的读取,这个过程是同步的,读取完数据后应用进程才能处理数据。
  • Proactor是异步网络模式,感知的是已完成的读写事件。在发起异步读写请求时,需要传入数据缓冲区的地址等信息,系统内核就可以自动帮我们将数据的读写请求工作完成,操作系统完成读写工作后,就会自动通知应用进程直接处理数据。

因此,Reactor可以理解为「来了事件操作系统通知应用进程,让应用进程来处理」,而Proactor可以理解为「来了事件操作系统来处理,处理完再通知应用进程」。

无论是Reactor还是Proactor,都是基于一种「事件分发」的网络编程模式,区别在于Reactor模式是基于「待完成」的I/O事件,而Proactor模式则是基于「已完成」的I/O事件

Proactor的示意图如下:

Proactor工作流程如下:

  • Proactor Initiator负责创建Proactor和Handler对象,并将Proactor和Handler通过Asychronous Operation Processor注册到内核
  • Asychronous Operation Processor负责处理注册请求,并处理I/O操作
  • Asychronous Operation Processor完成I/O操作后通知Proactor
  • Proactor回调对应的Handler进行业务处理
  • Handler完成业务处理

可惜的是,在Linux下的异步I/O是不完善的,aio系列函数是由POSIX定义的异步操作接口,不是真正的操作系统级别的支持,而是在用户空间模拟出来的异步,并且仅支持基于本地文件的aio异步操作,网络编程中的socket是不支持的,这也使得Linux的高性能网络程序都是使用Reactor方案。

Windows里实现了一套完整的支持socket的异步编程接口,叫做IOCP。由操作系统级别实现的异步I/O,是真正意义上的异步I/O。

4、总结

常见的Reactor实现方案有三中。

第一种是单Reactor单进程/线程,不考虑进程间通信以及数据同步的问题,实现起来简单,但是无法利用多核CPU,而且处理业务逻辑的时间不能太长,所以只适用于业务处理快速的场景,Redis的6.0版本之前采用的是单Reactor单进程的方案。(6.0版本之后是单线程执行 + 多线程 I/O 优化的改进型 Reactor 模型)

第二种方案单Reactor多进程/线程,解决了方案一的缺陷,但是在面对瞬时高并发的场景时,单Reactor会成为性能瓶颈。

第三种方案是多Reactor多进程/线程,主Reactor只负责监听事件,响应事件的工作交给了Reactor,Netty和Memcache都采用了「多Reactor多线程」的方案,Nigin采用了这种模式。

Reactor可以理解为「来了事件操作系统通知应用进程,让应用进程来处理」,而Proactor为「来了事件操作系统来处理,处理完通知进程」。

无论是Proactor还是Reactor,都是一种基于「事件分发」的网络编程模式。

5、参考博客

本篇博客个人学习、总结、摘抄至:[小林coding](9.3 高性能网络模式:Reactor 和 Proactor | 小林coding)

高性能的Reactor和Proactor模式学习的更多相关文章

  1. 高性能IO设计的Reactor和Proactor模式(转)

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作. 在比较这两个模式之前,我们首先的搞明白 ...

  2. Reactor和Proactor模式

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作.同步和异步 同步和异步是针对应用程序和内 ...

  3. Reactor和Proactor模式的讲解(关于异步,同步,阻塞与非阻塞)

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作. 在比较这两个模式之前,我们首先的搞明白 ...

  4. reactor与proactor模式

    在比较这两个模式之前,我们首先的搞明白几个概念,什么是阻塞和非阻塞,什么是同步和异步. 同步和异步是针对应用程序和内核的交互而言的. 同步是指用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 ...

  5. Reactor模式与Proactor模式

    该文章总结了网上资源对这两种模式的描述 原文地址:http://www.cnblogs.com/dawen/archive/2011/05/18/2050358.html 1.标准定义 两种I/O多路 ...

  6. 【转载】高性能I/O设计模式Reactor和Proactor

    转载自:http://blog.csdn.net/roger_77/article/details/1555170 昨天购买了<程序员>杂志 2007.4期,第一时间去翻阅了一遍,其中有一 ...

  7. I/O模型系列之四:两种高性能IO设计模式 Reactor 和 Proactor

    不同的操作系统实现的io策略可能不一样,即使是同一个操作系统也可能存在多重io策略,常见如linux上的select,poll,epoll,面对这么多不同类型的io接口,这里需要一层抽象api来完成, ...

  8. 同步异步阻塞非阻塞Reactor模式和Proactor模式 (目前JAVA的NIO就属于同步非阻塞IO)

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作. 在比较这两个模式之前,我们首先的搞明白 ...

  9. 【Network】一张图看懂 Reactor 与 Proactor 模型的区别

    首先来看看Reactor模式,Reactor模式应用于同步I/O的场景.我们以读操作为例来看看Reactor中的具体步骤:读取操作:1. 应用程序注册读就需事件和相关联的事件处理器2. 事件分离器等待 ...

  10. (原创)拨开迷雾见月明-剖析asio中的proactor模式(一)

    使用asio之前要先对它的设计思想有所了解,了解设计思想将有助于我们理解和应用asio.asio是基于proactor模式的,asio的proactor模式隐藏于大量的细节当中,要找到它的踪迹,往往有 ...

随机推荐

  1. 【SpringBoot Demo】MySQL + JPA + Hibernate + Springboot + Maven Demo

    主要包含:springboot+jpa+hibernate+mysql+lombok (两年前写过一个,现在重新记录一个) 1. 目录结构: 2. pom 文件 1 <?xml version= ...

  2. ASP.NET Core – Byte, Stream, Directory, File 基础

    前言 以前的文章: Stream 基础和常用 和 IO 常用. 这篇主要是做一个整理, 方便要用的时候 warm up. 之前有讲过 Bit, Byte 的基本概念: Bit, Byte, ASCII ...

  3. CSS & JS Effect – Image Overlay

    介绍 一张背景图, 一行写字, 一层黑影 (Image Overlay), 如果没有做黑影, 字的颜色容易和图片撞, contrast 就会很烂. HTML 结构 <div class=&quo ...

  4. Figma 学习笔记 – Scroll and Position Fixed

    Scroll Scroll 属于 prototype 的一部分. 当一个 Frame 的内容超出 Frame 的高度或宽度时, Frame 就具备了 scroll 的能力. 通过 uncheck cl ...

  5. [namespace hdk] string

    #include<bits/stdc++.h> using namespace std; namespace hdk{ const int siz=100001; class string ...

  6. nexus 部署与设置

    安装nexus df -h 先查看目录磁盘空间,我安装的版本占用了四个G 空间,目录文件空间不够导致启动失败 上传 nexus 压缩包,并解压 查询 8081 端口号是否被占用 sudo netsta ...

  7. Android dtbo(1) dto简介

    设备树 (DT, Device Tree) 是用于描述 non-discoverable(google这样写的,意思应该就是硬件信息看不到) 硬件的命名节点和属性构成的一种数据结构.操作系统(例如在 ...

  8. 使用Swagger的好处

    是一个规范和完整的框架 用于生成.描述.调用和可视化RESTful风格的Web服务 接口的文档在线自动生成 功能测试

  9. day08-数据类型拓展及面试题

    数据类型拓展及面试题 整数拓展----进制   //整数拓展----进制         int i=10;//十进制   不能以0开头,0~9         int i1=0b11;//二进制:0 ...

  10. AOT漫谈专题(第三篇): 如何获取C#程序的CPU利用率

    一:背景 1. 讲故事 上篇聊到了如何对AOT程序进行轻量级的APM监控,有朋友问我如何获取AOT程序的CPU利用率,本来我觉得这是一个挺简单的问题,但一研究不是这么一回事,这篇我们简单的聊一聊. 二 ...