Reactor 模型

Reactor 模型的 IO

  1. Reactor 模型,其 IO 属于同步非阻塞 IO。下面仍以 channel 发起读操作请求为例来分析整个执行过程:
  • 当 channel 的执行线程发起了 read()调用后,其会向 selector 注册了 OPS_READ 事件然后该线程会不停的查看该事件是否就绪。
  • 当 selector 接收到这个注册后,其就会不停的查看该 channel 所关联的网卡缓存中是否具有了数据。当 selector 轮询到该 channel 的网卡缓存中具有了数据后,该读操作就绪。
  • 此时该线程就查看到了就绪,会发起 system call,将网卡缓存中的数据读取到 user buffer中。这些操作完成后,会再执行 read()后面的逻辑。整个执行过程,该线程未发生阻塞。所以 Reactor 模型是“同步非阻塞 IO”模型。

Reactor 单线程模型

  1. ![](https://i.loli.net/2020/11/18/KbPTq8sUxHI2WQz.png#alt=image-20201118113959836)
  2. Reactor 单线程模型,指的是当前的 Sever 会为每一个通信对端形成一个 Channel,而所有这些 Channel 都会与一个线程相绑定,该线程用于完成它们间的所有通信处理。
  3. 线程主要的工作是:
  • 如果是 Server,则该线程需要接收并处理 Client 的连接请求。

  • 如果是 Client,则该线程需要向 Server 发起连接。

  • 读取通信对端的消息。

  • 向通信对端发送消息。

Reactor 线程池模型

  1. ![](https://i.loli.net/2020/11/18/4MOo2Kg7P8vtd5X.png#alt=image-20201118114523181)
  2. Reactor 单线程模型中使用一个线程处理所有通信对端的所有请求,在高并发场景中会严重影响系统性能。所以将单线程模型里面的这个单线程替换成了一个线程池,既 为每个Selector会为Channel分配一个线程池专门来处理这个Channel的请求以来提高在高并发场景下的性能。

Reactor 多线程池模型

Netty中的Reactor和Proactor模型 - 图1

  1. 如果客户端的连接的并发量是数以百万计的,而且 IO 操作还比较耗时,此时的 Server 即使采用的是 Reactor 线程池模型,系统性能也会急剧下降。此时,可以将连接操作与 IO 操作分开处理,形成 Reactor 的多线程模型。
  2. 当客户端通过处理连接请求的 Channel 连接上 Server 后,系统会为该客户端再生成一个子 Channel 专门用于处理该客户端的 IO 请求。这两类不同的 Channel 连接着两类不同的线程池。而线程池中的线程数量,可以根据需求分别设置。提高了系统性能。

Netty-Server Reactor 模型

Netty中的Reactor和Proactor模型 - 图2

  1. 中的每一个 EventLoop 都绑定着一个线程,用于处理该 Channel 与当前 Server 间的操作。Netty-Server 采用了多线程模型。不过线程池是由 EventLoopGroup 充当。EventLoopGroup一个 Channel 只能与一个 EventLoop 绑定,但一个 EventLoop 可以绑定多个 Channel。即 Channel EventLoop 间的关系是 n:1

Netty-Client Reactor 模型

Netty中的Reactor和Proactor模型 - 图3

  1. Netty-Client 采用的是线程池模型。因为其只需要与 Server 连接一次即可,无需区分连接请求与 IO 请求。

Proactor 模型

  1. 在高性能的网络通信设计中,有两个比较著名的网络通信模型 Reactor Proactor 模式,其中 Reactor 模式属于同步非阻塞 I/O 的网络通信模型,而 Proactor 运属于异步非阻塞 I/O的网络通信模型。
  2. Proactor 模型,其 IO 属于异步非阻塞 IO。下面仍以 channel 发起读操作请求为例来分析整个执行过程:
  • 当 channel 的执行线程调用了异步的 read()操作后,其会继续执行 read()后的逻辑。而该 read()会将本次操作注册到一个 Proactor 实例中,注册本次操作关注的事件为 Read Complete。

  • 一个 channel 的所有 IO 操作共享一个 Proactor 实例这个 Proactor 实例是在 channel 创建时完成的初始化。每个Proactor 实例具有一个绑定的线程,用于执行相关 IO 操作。

  • 当 Proactor 实例接收了 read()操作的注册后,其会为网络缓存注册一个监听。若网卡缓存中有了数据,则马上通过 DMA 控制将数据写入到 user buffer 中。一旦数据写入 user buffer完成,则该 IO 操作完毕,产生 Read Complete 事件,此时会将该事件写入到 Proactor 所维护的一个队列。Proactor 实例会将队列中的事件发送给各个 IO 调用者线程,以使他们触发相应的回调。

  • 当前 read()操作的调用线程无需阻塞等待 read()操作的完成,而是直接执行后面的逻辑。由于 read()操作本身是由另外一个线程来执行,所以 Proactor 模型是“异步非阻塞 IO”模型。

  • Reactor 中的 selector 是一种“事件分离器”的实现。在 Proactor 中不存在事件分离器,但存在一个 Proactor 实例。该实例会根据不同的 IO 操作,监听不同的内容。例如,本例为网卡缓存注册了监听。

Proactor 优缺点

  • Proactor 在处理高耗时 IO 时的性能要高于 Reactor,但对于低耗时 IO 的执行效率提升并不明显。
  • Proactor 的异步性使其并发处理能力要强于 Reactor。
  • Proactor 的实现逻辑复杂,编码成本较 Reactor 要高很多。
  • Proactor 的异步依赖于操作系统对于异步的支持。若操作系统对异步的支持不好,Proactor 的性能还不如 Reactor。

Netty Proactor

  • Netty4 是 NIO 的,其网络通信模型采用的是 Reactor。

  • Netty5 是 AIO 的,其网络通信模型采用的是 Proactor。但该版本已经被不再维护。主要原因还是 Linux 目前对于异步的支持不完善,导致其执行效率很低。

Proactor Epoll

  1. Proactor Epoll 是没有可比性的
  • Epoll:是“事件分离器”对就绪事件的发现方式,有 select、poll 与 epoll 三种方式。epoll采用的是回调方式,而不是轮询方式。Reactor 模型中具有“事件分离器”。

  • Proactor:是一种网络通信模型,该模型中就不存在“事件分离器”。

Netty 中的 Epoll 多路复用器

  1. epoll 的效率在如下场景中并不一定比 poll 的高。
  • 当出现大批量的读/写事件切换时,epoll 的效率会远远低于 poll。因为 epoll 需要进行大量的用户空间到内核空间的切换,而 poll 仅需要在用户空间做简单的位运算即可完成。
  • 若Client与Server端有大量的仅用于传递少量数据的短连接,则epoll的效率要低于poll。因为 epoll 下的每个 socket 连接都需要发生两次用户空间与内核空间的转换,而 poll 不需要。
  • epoll 完全属于 Linux,虽然其它系统平台也有 epoll 的支持,但并不完全相同。
  • 高性能处理的代码编写逻辑 epoll 要比 poll 更复杂,更难调试。特别是边缘触发。如果错过额外的读/写操作,很容易导致死锁。