Reactor 与 Proactor 线程模型详解

1. Reactor 模式

Reactor模式也叫 Dispatcher模式,其基于事件驱动处理模式,基本设计思想是I/O 复用结合线程池。下图为事件驱动处理示意图,可以看到大致流程为服务端程序监听处理多路请求传入的事件,并将事件分派给请求对应的处理线程完成处理

在这里插入图片描述

Reactor 模式中有 2 个关键组件:

  1. Reactor
    运行在一个单独的线程中,负责监听和分发事件,分发事件给适当的 Handler 来对 IO 请求做出反应
  2. Handler
    将自身与事件绑定,实际执行 I/O 要完成的事件。Reactor 通过调度适当的 Handler 来响应 I/O 事件,执行非阻塞操作

根据 Reactor 的数量和处理事件的线程数量的不同,Reactor 模式有 3 种实现

  1. 单 Reactor 单线程
  2. 单 Reactor 多线程
  3. 主从 Reactor 多线程

1.1 单 Reactor 单线程

实现的示意图如下,其消息的处理流程可概括为以下几步:

  1. Reactor 通过 select 监控连接上的所有事件,收到事件后通过 dispatch 将其分发
  2. 如果该事件是请求连接建立,则由 Acceptor 接受连接,并为其创建 Handler 处理后续事件
  3. 假如不是建立连接事件,Reactor 将其分发给对应的 Handler 来响应
  4. Handler 实际处理事件,完成 read->handle->send 的业务处理流程

在这里插入图片描述

单Reactor单线程模型只是进行了代码组件的区分,整体操作还是单线程,其优缺点如下:

  • 优点
    模型简单,所有处理都在一个线程中完成,没有多线程上下文切换的开销,没有进程通信及锁竞争的问题
  • 缺点
  1. 只有一个线程,无法完全发挥多核 CPU 的性能,造成浪费
  2. Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,容易导致性能瓶颈
  3. 一旦 Reactor 线程意外中断或者跑飞,可能导致整个系统通信模块不可用,无法接收和处理外部消息,造成节点故障
  • 适用场景
    客户端数量有限,业务处理非常快速,例如 Redis 就是使用单 Reactor 单线程模型

1.2 单 Reactor 多线程

实现的示意图如下所示,消息处理流程可以分为以下几步:

  1. Reactor 通过 select 监控连接上的所有事件,收到事件后通过 dispatch 将其分发
  2. 如果是请求建立连接的事件,则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 处理该连接,完成后续的读写事件
  3. 假如不是建立连接事件,Reactor 将其分发给对应的 Handler 来响应。该实现中 Handler 只负责响应事件,read 读取数据后会将其分发给 Worker 线程池进行 handle 业务处理
  4. Worker 线程池调度线程完成实际的业务处理,并将响应结果返回给主线程 Handler,Handler 通过 send 将结果返回给请求端,完成请求响应的流程

在这里插入图片描述
单 Reactor 多线程模型 相对于单Reactor单线程模型来说,handle 业务逻辑交由线程池来处理,其优缺点如下:

  • 优点
    可以充分利用多核 CPU 的处理能力,使 Reactor 更专注于事件分发工作,提升整个应用的吞吐
  • 缺点
  1. 多线程环境下数据共享和访问比较复杂,子线程完成业务处理后把结果传递给主线程 Handler 进行发送,需要考虑共享数据的互斥和保护机制
  2. Reactor 主线程单线程运行,承担所有事件的监听和响应,高并发场景下会成为性能瓶颈

1.3 主从 Reactor 多线程

单 Reactor 多线程模型中 Reactor 在单线程中运行,高并发场景下容易成为性能瓶颈,针对这个缺点一个解决方案是让 Reactor 在多线程中运行,于是产生了主从 Reactor 多线程模型。相比第二种模型,它将 Reactor 分成两部分:

  1. MainReactor 只用来处理网络IO连接建立的操作,并将建立的连接指定注册到 SubReactor 上
  2. SubReactor 负责处理注册其上的连接的事件,完成业务处理,通常 SubReactor 个数可与CPU个数等同

主从 Reactor 多线程模型消息处理流程可以分为以下几个步骤:

  1. Reactor 主线程 MainReactor 通过 select 监控建立连接的事件,收到事件后通过 Acceptor 接收,处理建立连接事件
  2. Acceptor 建立连接后,MainReactor 将连接分配给 Reactor 子线程 SubReactor 进行处理。SubReactor 会将该连接加入连接队列进行监听,并创建一个 Handler 用于处理该连接上的读写事件
  3. 当有读写事件发生时,SubReactor 调用连接对应的 Handler 进行响应,Handler 读取数据后将其分发给 Worker 线程池进行 handle 业务处理
  4. Worker 线程池调度线程完成实际的业务处理,并将响应结果返回给 SubReactor 的 Handler,Handler 通过 send 将结果返回给请求端,完成请求响应的流程

在这里插入图片描述
这种 Reactor 实现模型使用非常广泛,比较著名的包括 Nginx 主从 Reactor 多进程模型,Memcached 主从多线程,Netty 主从多线程模型的支持,其主要优点如下:

  1. MainReactor 与 SubReactor 的数据交互简单,MainReactor 只需把新连接交给 SubReactor,SubReactor 无需返回数据
  2. MainReactor 与 SubReactor 主从职责明确,MainReactor只负责接收新连接,SubReactor 负责完成后续的业务处理

2. Proactor 模式

在 Reactor 模式中,Reactor 在用户进程通过 select 轮询等待某个事件的发生,然后将这个事件分发给 Handler,由 Handler 来做实际的读写操作。这个过程中读写操作依然是同步的,如果把 I/O 操作改为异步,即交给操作系统来完成,就能进一步提升性能,这就是异步网络模型 Proactor

Proactor模式也是基于事件驱动模型,不过 Proactor 不关注读取就绪事件,而是关注读取完成事件,因为异步IO都是操作系统将数据读写到指定的缓冲区,应用程序直接从缓冲区取用即可。以下为示意图,其各个模块组成如下:

  1. Procator Initiator
    负责创建 Procator 和 Handler,并将 Procator 和 Handler 注册到内核
  2. Asynchronous Operation Processor
    负责处理注册请求,并进行 IO 操作,IO 操作完成后会通知 Procator
  3. Procator
    Procator 在内核进程等待 IO 完成的事件,根据事件类型回调对应的 Handler 进行业务处理。Handler 负责完成实际的业务处理,也能注册新的 Handler 到内核

在这里插入图片描述
以读取操作为例,Proactor模式的消息处理流程如下:

  1. 应用程序初始化 Proactor 和相应的 Handler,然后将其注册到内核
  2. 请求端发送数据,操作系统调用内核线程完成读取操作,并将读取的内容放入指定的缓冲区
  3. Proactor 在内核进程中等待读取操作完成事件,捕获到读取完成事件后,回调相应的 Handler
  4. Handler 直接从缓冲区读取数据处理,已经不需要进行实际的读取操作

综上可以明白 Proactor 与 Reactor 的区别:

  1. Reactor 由应用程序自行轮询监听事件,Proactor 则由内核进程监听事件,开销更小
  2. Reactor 读写的 IO 操作由应用程序自行完成处理,Proactor 是由内核异步 IO 完成读写操作,再发出通知

理论上 Proactor 比 Reactor 效率更高,但是 Proactor 有如下缺点:

  1. 内存使用
    缓冲区在读写操作的时间段内必须持续占用内存,并且每个并发操作都要求有独立的缓冲区,相比 Reactor 模式在准备好读写前不要求开辟缓存,Proactor 对内存提出了更多的要求
  2. 操作系统支持
    异步 I/O 依赖操作系统支持,Windows 中通过 IOCP 实现了真正的异步 I/O,而在 Linux 系统中异步 I/O 目前还不完善

猜你喜欢

转载自blog.csdn.net/weixin_45505313/article/details/106813785