一、单线程 Reactor 模式流程

image.png

  1. 服务器端的Reactor是一个线程对象,该线程会轮训所有事件,并使用Selector(选择器)来实现 IO 的多路复用。向Reactor中注册一个Acceptor事件处理器,Acceptor事件处理器所关注所有ACCEPT事件,Reactor会监听客户端向服务器端发起的连接请求事件(ACCEPT事件)。
  2. 客户端向服务器端发起一个连接请求,Reactor监听到 ACCEPT事件的发生,并将ACCEPT 事件派发给相应的 Acceptor处理器来进行处理。Acceptor 处理器通过accept()方法得到客户端对应的连接(SocketChannel),然后将该连接所关注的READ事件以及对应的READ事件处理器注册到Reactor中,这样Reactor就会监听该连接的READ事件了。
  3. 当 Reactor 监听到有读或者写事件发生时,将相关的事件派发给对应的处理器进行处理。比如,读处理器会通过 SocketChannel的read()方法读取数据,此时 read()操作可以直接读取到数据,而不会堵塞与等待可读的数据到来。
  4. 每当处理完所有就绪的感兴趣的 I/O事件后,Reactor 线程会再次执行select()阻塞等待新的事件就绪并将其分派给对应处理器进行处理。

注意,Reactor的单线程模式的单线程主要是针对于I/O 操作而言,也就是所有的 I/O 的accept()、read()、write()以及 connect()操作都在一个线程上完成的。
但目前的单线程 Reactor 模式中,不仅 I/O 操作在该Reactor 线程上,非 I/O 的业务操作也在该线程上进行处理,这可能会大大延迟 I/O 请求的响应。所以我们应该将非 I/O的业务逻辑操作从 Reactor 线程上卸载,以此来加速 Reactor 线程对 I/O 请求的响应。

二、单线程 Reactor之工作者线程池

image.png
在单线程 Reactor 模式基础上,添加一个工作者线程池,并将非 I/O 操作从Reactor线程中移出转交给工作者线程池来执行。这样能够提高 Reactor 线程的 I/O 响应,不会因为业务逻辑而延迟对后面 I/O 请求的处理。

使用线程池的优势

  1. 通过复用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程产生的巨大开销。
  2. 另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会等待创建线程而延迟任务的执行,从而提高了响应性。
  3. 通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保持忙碌状态。 同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败。

    存在问题

    当前版本中,所有I/O 操作依旧由一个 Reactor 来完成,包括 I/O 的 accept()、read()、write()以及 connect()操作。
    当前版本一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发或大数据量的应用场景却不合适,主要原因如下:

  4. 一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的 CPU 负 荷达到 100%,也无法满足海量消息的读取和发送;

  5. 当 NIO 线程负载过重时,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO 线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;

    三、主从Reactor 之工作线程池模式

    image.png
    Reactor线程池中的每一Reactor线程都有自己的 Selector、线程和分发的事件循环逻辑
    mainReactor可以只有一个,但 subReactor 一般会有多个。mainReactor 线程主要负责接收客户端的连接请求,然后将接收到的SocketChannel传递给 subReactor,由 subReactor 来完成和客户端的通信。
    流程:

  6. 注册一个 Acceptor事件处理器到mainReactor 中,Acceptor事件处理器所关注的事件是 ACCEPT 事件, mainReactor会监听客户端向服务器端发起的连接请求事件(ACCEPT事件)。

  7. 客户端向服务器端发起一个连接请求,mainReactor监听到ACCEPT事件并将该ACCEPT事件派发给 Acceptor处理器来进行处理。Acceptor 处理器通过accept()方法得到与这个客户端对应的连接(SocketChannel),然后将这个 SocketChannel传递给subReactor线程池
  8. subReactor 线程池分配一个subReactor线程给这个SocketChannel,并将SocketChannel关注的 READ 事件以及对应的READ 事件处理器注册到 subReactor 线程中。也可以注册 WRITE 事件以及 WRITE 事件处理器到 subReactor 线程中以完成 I/O 写操作。
  9. 当有I/O 事件就绪时,相关的subReactor就将事件派发给响应的处理器处理。注意,subReactor 线程只负责完成 I/O 的 read()操作,在读取到数据后将业务逻辑的处理放入到线程池中完成,若完成业务逻辑后需要返回数据给客户端,则相关的 I/O 的write操作会被提交回 subReactor 线程来完成。

总结
所有的 I/O操作(I/O 的 accept()、read()、write()以及 connect()操作)依旧还是在Reactor线程(mainReactor 线程 或 subReactor 线程)中完成的。Thread Pool(线程池)仅用来处理非 I/O 操作的逻辑。
多Reactor线程模式将“接受客户端的连接请求”和“与该客户端的通信”分在了两个Reactor 线程来完成。mainReactor 完成接收客户端连接请求的操作,它不负责与客户端的通信,而是将建立好的连接转交给 subReactor 线程来完成与客户端的通信,这样不会因为read()数据量太大导致后面的客户端连接请求得不到及时处理的情况。并且多 Reactor线程模式在海量的客户端并发请求的情况下,可以通过subReactor线程池来将海量的连接分发给多个subReactor线程,在多核的操作系统中这能大大提升应用的负载和吞吐量。

四、Reactor 模式与观察者模式比较

观察者模式:
也称为 发布-订阅 模式,主要适用于多个对象依赖某一个对象的状态并,当某对象状态发生改变时,要通知其他依赖对象做出更新。是一种一对多的关系。如果依赖的对象只有一个时,也是一种特殊的一对一关系。通常,观察者模式适用于消息事件处理,监听者监听到事件时通知事件处理者对事件进行处理(这一点上有点像是回调,容易与反应器模式和前摄器模式的回调搞混淆)。
Reactor 模式:
reactor 模式,即反应器模式,是一种高效的异步IO模式,特征是回调,当 IO 完成时,回调对应的函数进行处理。这种模式并非是真正的异步,而是运用了异步的思想,当 IO 事件触发时,通知应用程序作出 IO 处理。模式本身并不调用系统的异步IO函数。
reactor模式与观察者模式有点像。不过,观察者模式单个事件源关联,而反应器模式则与多个事件源关联 。当一个主体发生改变时,所有依属体都得到通知。