Reactor模式&Proactor模式

1.Reactor/Dispatcher模式

1.1 概述

Reactor模式下,服务端的构成为Reactor + 处理资源池。其中,Reactor负责监听和分发事件,而处理资源池则负责处理事件。
该模式下的组合方案有下面几种(第三种几乎没有被实际应用):

  1. 1 * Reactor + 1 * Worker。
  2. 1 * Reactor + n * Worker。
  3. n * Reactor + 1 * Worker
  4. n * Reactor + n * Worker。

注:Worker是指负责处理事件的工作进程/线程。

n * Reactor + 1 * Worker 没有应用的原因(类比Redis为什么始终坚持单线程处理(执行)指令的原因):

  1. 复杂且没有性能优势。
    1. 复杂:多个Reactor去接收用户请求,这就需要处理好线程并发问题。
    2. 没有性能优势:多线程并发,如果是在单核上,可能会面临频繁的线程上下文切换,开销较大。其次,由于我们基本上是在内存中进行请求接收的,因此主要的瓶颈不在于线程数量而在于网络时延&带宽。
  2. 多个reactor去负责接待请求,而真正服务请求的时候确实串行的,你不觉得?。。。

1.2 实现方式

1.2.1 1 * Reactor + 1 * Worker

1.2.1.1 概述


该模式下的三种角色:

  • reactor:负责监听和分发事件。
  • acceptor:获取连接。
  • handler:处理事件。

执行流程

  1. reactor通过select系统调用持续监听IO事件,若监听到有事件发生,则根据事件类型进行分情况处理:
    1. 若为连接事件:则将该事件**分发(dispatch)**给acceptor处理,acceptor在收到该事件后,会通过accept系统调用新建一个连接并同时创建一个handler来处理后续的事件。
    2. 若不是连接事件:则交由handler进行事件处理。
1.2.1.2 评估
  • 该方案不适用于计算密集型场景,仅适用于业务耗时短的场景。
  • 由于全程采用单进程/线程监听&处理任务,因此无法充分利用多核CPU的性能。而且,一旦事件消费的过程中,某个事件的消费耗时特别长,它将影响到后续事件的响应,一般体现在延迟的增加方面。

C语言编写的服务端程序,其模式为1 * Reactor + 1 * 进程,而Java的则是1 * Reactor + 1 * 线程(JVM是一个进程,你所写好的Java程序是其中的一个线程)。
Redis 6.0- 采用的是1 * Reactor + 1 * 进程

1.2.2 1 * Reactor + n * Worker

1.2.2.1 概述


与前面不同的是:
handler不在负责事件的处理,而是负责数据的接收与发送。
handler在通过read系统调用拿到数据后,会将其转交给子线程中的processor进行处理,然后processor处理完成后,再将处理好的数据返回给handlerhandler再通过send系统调用将结果发回客户端。

1.2.2.2 评估
  • 该方案能够充分利用多核CPU的性能。
  • 由于该方案采用多进程/线程来进行事件的处理,因此需要注意在多线程环境下的共享数据的安全问题,而这实现起来,因此该方案较前者复杂一些。
  • 需要注意在高并发环境下,1个reactor可能会成为性能瓶颈的隐患。

1.2.3 n * Reactor + n * Worker

1.2.3.1 概述


与前面不同的是:
handler又再次负责事件的处理了。
对于新的连接事件,将首先分发给acceptor,然后acceptor创建出一个连接来,并将其分发给众多子线程中的其中一个线程。
被选中的这个线程中的reactor将通过select系统调用对该连接进行持续监听,一旦监听到有IO事件发生,便分发给该线程所对应的handler去处理…(后续处理过程一样的)

1.2.3.2 评估
  • 消除了单reactor所带来的潜在的性能瓶颈隐患。
  • 该方案看上去比前者复杂,其实其结构是清晰的,实施起来是简单的:
    • 主线程、子线程分工明确,主线程负责接收新连接,子线程负责事件处理。
    • 不必为主线程与子线程之间的通信而感到苦恼,因为数据流是由主线的单向流动到子线程中,即在子线程拿到新连接,监听并处理好一个IO事件后,不必将数据再返回给主线程,而是直接由子线程自己返回给客户端。

Netty、Memcache、Nginx均采用了此方案。
Nginx的方案与上面的并不完全相同,它选择去掉主线程部分,即连接的接收可以由每个子线程来完成。

2.Proactor模式

2.1 概述


执行流程:

  1. 进程通过Proactor Initiator借助Asynchronous Operatio Processor注册proactorhandler到内核上。
  2. 后面将由Asynchronous Operatio Processor 负责请求的接收与IO操作,一旦接收到IO事件,它将自动进行IO操作,当IO完成后,它将通知Proactor,然后Proactor再根据具体的事件类型回调handler进行处理。

2.2 评估

  • 实现了异步I/O,即数据的准备和由内核拷贝至用户缓冲区的过程均无需用户进程(或者CPU)参与。

目前仅有Windows系统平台完全实现系统级别下的了异步I/O——IOCP
Linux平台尽管也有POSIX定义的异步IO接口aio函数,但它是用户层面的实现,而且仅支持本地文件的异步IO操作,不支持网络I/O。但是,在Linux 5.1之后,又引入io_uring异步I/O操作接口,它绝对是一个实打实的系统基本的实现,并且也可以用于网络I/O,并且它无需中断即可实现I/O操作,非常惊艳。

3.两种模式的对比

  • reactor模式下,reactor所感知到的事件是待完成的I/O事件,后续handler处理需要先把数据由内核缓存拷贝至用户缓冲区中才能继续处理事件,这种I/O模式属于非阻塞式I/O同步I/O
  • proactor模式下,proactor所感知到的事件是已完成的I/O事件,即不需要handler再去拷贝数据了,而是直接去处理事件,这种I/O模式属于非阻塞式I/O异步I/O,效率更高。

参考文档

9.3 高性能网络模式:Reactor 和 Proactor


  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程旧事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值