图解 Reactor 和 Proactor
Reactor
Reactor 和 Proactor 是两个高性能网络模式。
Reactor 模式也叫 Dispatcher 模式,即 I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程/线程。
Reactor 模式主要由 Reactor 和处理资源池这两个核心部分组成,它两负责的事情如下:
- Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
- 处理资源池负责处理事件,如 read->业务逻辑->send
Reactor 模式是灵活多变的,可以应对不同的业务场景,灵活在于:
- Reactor 的数量可以只有一个,也可以有多个
- 处理资源池可以是单线程/进程,也可以是多线程
将上面的两个因素排列组设一下,理论上就可以有 4 种方案选择:
- 单 Reactor 单进程 / 线程;
- 单 Reactor 多进程 / 线程;
- 多 Reactor 单进程 / 线程;
- 多 Reactor 多进程 / 线程;
其中,「多 Reactor 单进程 / 线程」实现方案相比「单 Reactor 单进程 / 线程」方案,不仅复杂而且也没有性能优势,因此实际中并没有应用。剩下的 3 个方案都是比较经典的,且都有应用在实际的项目中。
单 Reactor 单进程/线程
示意图如下:
可以看到进程里有三个对象:
- Reactor 对象的作用是监听和分发事件
- Acceptor 对象的作用是获取连接
- Handler 对象的作用是处理业务
该方案的缺点:
- 因为只有一个进程,无法充分利用多核CPU的性能
- Handler 对象在业务处理时,整个进程是无法处理其他的连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟。
所有,单 Reactor 单进程的方案不适合计算机密集型的场景,只适用于业务处理非常快速的场景,比如 Redis 就采用了这种方案。
单 Reactor 多线程/多进程
示意图如下:
与上一种方案不同的是:
- Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过read读取到数据后,会将数据发送给子线程里的 Processor对象进行业务处理
- 子线程里的Processor 对象就进行业务处理,处理完毕后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client。
该模型的优势:
- 能够充分利用 CPU 多核的性能
缺点:
- 多个线程就会涉及共享数据的竞争,要避免竞争导致数据错乱,需要加互斥锁
- 因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。
多 Reactor 多线程/多进程
示意图如下:
说明如下:
- 主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
- 子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。
- 如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。
- Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
大名鼎鼎的两个开源软件 Netty 和 Memcache 都采用了「多 Reactor 多线程」的方案。
Proactor
Reactor 是非阻塞同步网络模型,而 Proactor 是异步网络模型
-
Reactor 是非阻塞同步网络模式,感觉的是就绪可读写事件。在每次感知到有事件发生(比如可读就绪事件)后,就需要应用进程主动调用 read 方法来完成数据的读取,也就是要应用进程主动将 socket 接收缓存中的数据读到应用进程内存中,这个过程是同步的,读取完数据后应用进程才能处理数据。
-
Proactor 是异步网络模式, 感知的是已完成的读写事件。在发起异步读写请求时,需要传入数据缓冲区的地址(用来存放结果数据)等信息,这样系统内核才可以自动帮我们把数据的读写工作完成,这里的读写工作全程由操作系统来做,并不需要像 Reactor 那样还需要应用进程主动发起 read/write 来读写数据,操作系统完成读写工作后,就会通知应用进程直接处理数据。
因此,Reactor 可以理解为「来了事件操作系统通知应用进程,让应用进程来处理」,而 Proactor 可以理解为「来了事件操作系统来处理,处理完再通知应用进程」。
Reactor 模式是基于「待完成」的 I/O 事件,而 Proactor 模式则是基于「已完成」的 I/O 事件。
看一下Proactor 模式示意图:
aio 系列函数是由 POSIX 定义的异步操作接口,不是真正的操作系统级别支持的,而是在用户空间模拟出来的异步,并且仅仅支持基于本地文件的 aio 异步操作,网络编程中的 socket 是不支持的,这也使得基于 Linux 的高性能网络程序都是使用 Reactor 方案。
而 Windows 里实现了一套完整的支持 socket 的异步编程接口,这套接口就是 IOCP,是由操作系统级别实现的异步 I/O,真正意义上异步 I/O,因此在 Windows 里实现高性能网络程序可以使用效率更高的 Proactor 方案。
巨人的肩膀: