【图解IO与Netty系列】Reactor模型

Reactor模型

Reactor模型简介

Reactor模型是服务器端用于处理高并发网络IO请求的编程模型,与传统的一请求一线程的同步式编程模型不同的是,Reactor模型是基于事件驱动的响应式编程模型,可以一个线程处理多个请求,并且是异步处理,在高并发场景下,性能大大的提升。

传统的同步式编程模型如下:

服务端接收到请求后,从线程池中拿出一个线程(比如Tomcat里的线程池),经过Controller、Service、Dao的一顿处理,最后返回响应结果给客户端。这种同步处理请求的方式效率并不高,可以应对一些并发量不高的场景。

基于事件驱动的响应式编程模型如下:

  1. 服务端启动时,会创建Reactor反应器,然后会向Reactor注册基本的事件类型与对应的Handler(比如网络编程时的连接就绪事件与Acceptor)
  2. 当服务端接收到客户端的请求时,服务端的Reactor就有事件就绪,Reactor会获取到就绪的事件,在高并发场景下,Reactor有可能获取到一批就绪的事件
  3. Reactor进事件分发,把事件分派给与之绑定的Handler中,Handler对事件的处理可以在当前线程中也可以在线程池中,当然在线程池中处理性能会更高
  4. Handler处理完毕后,如果后续还有其他处理步骤,则可以继续注册新的事件与对应的Handler到Reactor中;如果没有后续,则可以返回响应结果给客户端,返回响应结果这个动作可作为单独的一个事件,由对应的Handler进行处理,那么也可以继续注册到Reactor中。

Reactor其实就是一个线程,可以看到它就在一个死循环中进行事件监听以及事件分派,这就是事件循环。

Reactor模型底层使用了IO多路复用,通过对IO多路复用机制的合理利用,使得服务器端达到高效的处理网络IO请求的目的。我们可以使用Java提供的NIO来实现Java版本的Reactor模型。

三类事件与三类角色

Reactor模型抽象了三类事件和三类角色,它们是Reactor模型的核心组成部分。三类事件是:连接就绪事件,读就绪事件、写就绪事件。三类角色是:Reactor、Acceptor、Handler。

首先我们来了解一下三类事件。

  • 连接就绪事件:客户端向服务端发起连接的时候,对应的Channel就有连接就绪事件发生。
  • 读就绪事件:当客户端向服务器端发送数据时,对应的Channel就有读就绪事件发生。
  • 写就绪事件:当服务器处理完客户端发送的数据,需要返回结果时,可以向Selector注册一个写就绪事件并与对应的Channel关联,那么对应Channel就有写就绪事件发生。

我们再来了解一下三类角色。

Reactor是相当于是一个事件分派器,Reactor专门负责监听事件的发生,并把发生的事件交给对应的处理器处理,这里的处理器指的就是Acceptor或Handler。Reactor是一个线程,它通过Selector注册并监听多个Channel,如果监听到有事件发生,判断是连接就绪事件,会交给Acceptor处理,如果发生的事件是读就绪事件或写就绪事件,会交给Handler处理。

如果是连接就绪事件,Reactor会将其分派给Acceptor处理,Acceptor会调用accept()方法获取到连接对应的SocketChannel,然后会将其设置为非阻塞,注册到Reactor的Selector中,设置关注的事件为读就绪事件。

如果是读就绪事件,那么Reactor会将其分派给与其绑定的Handler处理,该Handler会经过read、decode、compute、encode、send五个步骤的处理。

  1. read:读取Channel中的数据
  2. decode:对读到的数据进行解码
  3. compute:解码后的数据进行相应处理
  4. encode:对处理后的结果进行编码
  5. send:编码后的数据发送给客户端。

之所以要进行decode和encode,是因为数据在网络上是以二进制的形式传输的,因此服务端接收到后要对二进制字节码进行解码操作,然后才能对解码后的数据进行处理,处理完的数据要发送到网络,也要编码成二进制的格式。

其中,send操作可以立刻发送数据写出到Channel,也可以注册一个写就绪事件到Selector,这就相当于进行异步发送。

Reactor模型整体流程

  1. 首先,一开始我们只把ServerSocketChannel以及连接事件处理器Acceptor注册到Reactor中,Reactor会把ServerSocketChannel注册到Selector中,并设置关注连接就绪事件
  2. 一旦客户端发起连接,ServerSocketChannel的连接就绪事件就绪,Reactor会将其分派给Acceptor处理,Acceptor会获取到连接对应的SocketChannel,并将其与对应的处理器Handler注册到Reactor,Reactor会将其注册到Selector中。
  3. 随后客户端向服务端发送数据,Reactor监听到对应的SocketChannel有读就绪事件发生,会将其分派给与其绑定的Handler进行处理。
  4. Handler被分派到读就绪事件时,会经过read、decode、compute、encode、send五个步骤的处理。
  5. 服务端要把处理结果返回给客户端,会向Reactor注册一个对应Channel的写就绪事件,Reactor监听到写就绪事件,会分派给与该Channel绑定的Handler处理,Handler把返回结果发送出去。

各种Reactor模型

Reactor编程模型不是只有单一的一种编程模型,它有单Reactor单线程模型、单Reactor多线程模型、主从Reactor模型等多种模型。

单Reactor单线程模型

单Reactor单线程模型是最简单的模型,当然性能也是最低的。单Reactor模型只有一个Reactor,这个Reactor即负责处理连接建立,也负责数据读写和计算等处理。并且由于是单线程模型,所以数据读取后的计算处理,也是在当前线程中完成。

这种单Reactor单线程模型虽然性能比其他的Reactor模型低,但是优点是比较的简单,没有了多线程就意味着没有了"多线程翻车"的一些情况出现。值得一提的是,Redis的事件驱动框架就是单Reactor单线程模型

但是由于连接建立与IO处理都在一个Reactor线程中进行,因此在高并发场景下,该Reactor线程的压力会非常大,特别是在数据计算还比较复杂的场景,单线程的Reactor模型有可能支撑不住。之所以Redis使用单Reactor单线程模型还能支撑高并发读写的场景,其中一个原因是因为Redis内部不涉及太复杂的数据计算。

单Reactor多线程模型

单Reactor多线程模型是在单Reactor模型的基础上增加了线程池,可以把decode、compute、encode等需要计算的操作提交到线程池执行,而Reactor线程则专注于连接建立与IO的处理(也就是read和send)。

把涉及到计算的处理抽到了线程池中处理,可以在一定程度上缓解Reactor线程的压力。但是由于还是单Reactor模型,IO处理依然在该Reactor线程中进行,高并发场景下,可能会由于大量的IO请求需要处理而导致连接建立的处理受到影响,该Reactor线程可能无法及时响应客户端连接建立的请求。

主从Reactor模型

主从Reactor模型在单Reactor多线程模型基础上做了改进,把IO处理分离到了子Reactor中,主Reactor只专注于连接请求的处理,主Reactor一般只有一个(也可有多个),而子Reactor一般有多个。当主Reactor上有连接就绪事件发生时,通过Acceptor获取到连接对应的SocketChannel,然后把该SocketChannel注册到子Reactor中,由子Reactor监听该Channel的读就绪事件并处理。

由于连接建立与IO处理分离到了不同的Reactor,当有大量并发的IO请求需要处理时,也不会影响到客户端建立连接的请求。

相关推荐
所待.3832 分钟前
JavaEE之线程初阶(上)
java·java-ee
Winston Wood6 分钟前
Java线程池详解
java·线程池·多线程·性能
速盾cdn7 分钟前
速盾:CDN缓存的工作原理是什么?
网络·安全·web安全
Alive~o.07 分钟前
Go语言进阶&依赖管理
开发语言·后端·golang
手握风云-10 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
许苑向上13 分钟前
Dubbo集成SpringBoot实现远程服务调用
spring boot·后端·dubbo
喵叔哟30 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生36 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
网络安全-杰克1 小时前
网络安全概论
网络·web安全·php
郑祎亦1 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis