IO之Reactor模式

说起Reactor模式我们知道的就是在Java中的NIO就是基于Reactor模式。
今天我们来学习一下Reactor模式的原理。

在处理Web请求时,通常有两种体系结构,分别为:thread-based architecture(基于线程)event-driven architecture(事件驱动)

Thread-based architecture 基于线程

这样的体系结构一般就是使用多线程来处理客户端的请求,每当接收到一个请求,就开启一个独立的线程来处理。
但是当并发量大的时候,线程又要占用一定的内存资源,线程的上下文切换也需要一定的开销,所以这样显然降低了Web 服务器的性能。而且,线程在等待处理IO的这段时间内线程空闲,也会造成CPU资源的浪费。
在这里插入图片描述

Event-driven architecture

事件驱动的体系结构目前使用比较广泛。该体系结构中,会定义一系列的事件驱动器来响应事件的发生,并将服务端接受连接 和 对事件的处理 分离。
(事件指的是一种状态的改变)
比如,tcp中socket的new incoming connection、ready for read、ready for write

Reactor

Reactor模式就是Event-driven architecture的一种实现方式,处理多个客户端并发向服务端请求服务的场景。
每个服务在服务端可能有多个方法组成,reactor会解耦并发请求中的服务并分发给对应的事件处理器来处理。
目前许多流行的开源框架都用到了Reactor模式:netty、Node.js还有NIO。
在这里插入图片描述
Reactor 主要由以下几个角色构成:handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler。
各个角色和他们之间的关系:

在这里插入图片描述
接下来我们来讲一讲这几个角色:

Handle

在linux中Handle被称为文件描述符,而在Windows中称为句柄
handle就是事件的发源地:比如一个网络socket、磁盘文件等,发生在handle上的事件可以有connection \ ready for read \ ready for write。

Synchronous Event Demultiplexer /di:,mʌlti’pleksə/

同步事件分离器,本质上是系统调用。比如是Linux中的select\poll\epoll等。
比如,NIO中的Selector中的select方法会一直阻塞到handle上有事件发生时才会返回。

Event Handler

事件处理器,其会定义一些回调函数或者称为钩子函数,当handle上有事件发生时,回调方法就会执行,这是一种事件处理机制。

Concrete Event Handler

具体的事件处理器,实现了Event Handler。在回调方法中会实现具体的业务逻辑

Initiation Dispatcher

初始分发器,也是reactor角色,提供了注册、删除与转发event handler的方法。当Synchronous Event Demultiplexer检测到handle上有事件发生时,便会通知initiation dispatcher调用特定的event handler的回调方法。

整个处理流程:

读取操作:

  1. 应用程序注册读就绪事件和相关联的事件处理器

  2. 事件分离器等待事件的发生

  3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器

  4. 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理

写入操作类似于读取操作,只不过第一步注册的是写就绪事件。
在这里插入图片描述
在这里插入图片描述

  1. 当应用向Initiation Dispatcher注册Concrete Event Handler时,应用会标识出该事件处理器希望Initiation Dispatcher在某种类型的事件发生发生时向其通知,事件与handle关联
  2. Initiation Dispatcher要求注册在其上面的Concrete Event Handler传递内部关联的handle,该handle会向操作系统标识
  3. 当所有的Concrete Event Handler都注册到 Initiation Dispatcher上后,应用会调用handle_events方法来启动Initiation Dispatcher的事件循环,这时Initiation Dispatcher会将每个Concrete Event Handler关联的handle合并,并使用Synchronous Event Demultiplexer来等待这些handle上事件的发生
  4. 当与某个事件源对应的handle变为ready时,Synchronous Event Demultiplexer便会通知 Initiation Dispatcher。比如tcp的socket变为ready for reading
  5. Initiation Dispatcher会触发事件处理器的回调方法。当事件发生时, Initiation Dispatcher会将被一个“key”(表示一个激活的handle)定位和分发给特定的Event Handler的回调方法
  6. Initiation Dispatcher调用特定的Concrete Event Handler的回调方法来响应其关联的handle上发生的事件。

Reactor的演化

从上图可以看出,Reactor之前是单线程,需要处理多个accept连接,同时发送请求到处理器中。
改进使用多线程处理业务逻辑:将处理器的执行放入线程池,多线程进行业务处理。但Reactor仍为单个线程。

在这里插入图片描述

继续改进:对于多个CPU的机器,为充分利用系统资源,将Reactor拆分为两部分。


mainReactor负责监听连接,accept连接给subReactor处理,为什么要单独分一个Reactor来处理监听呢?因为像TCP这样需要经过3次握手才能建立连接,这个建立连接的过程也是要耗时间和资源的,单独分一个Reactor来处理,可以提高性能。

Reactor的优缺点

优点:
1)响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

缺点:
1)相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试
2)Reactor模式需要底层的Synchronous Event Demultiplexer(同步事件分离器)支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
3) Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式

资料:https://www.jianshu.com/p/eef7ebe28673
https://www.cnblogs.com/doit8791/p/7461479.html
在这里插入图片描述在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/mulinsen77/article/details/89041040