0、引言
在上一篇的笔记中,我们学习了操作系统提供的高效I/O管理技术,主要用于解决服务器在高并发场景下的资源浪费和瓶颈问题。但是在实际的代码编写中,要是我们都全部调用底层的I/O多路复用接口来编写网络程序这种面向过程的方式,必然会导致开发的效率不高。于是在这一章节,我们来学习两个重要的Reactor和Proactor模型,他们都借用了I/O多路复用机制来高效地管理和分发事件,并且更利于程序员进行程序开发的代码编写。
1、Reactor和Proactor
基于面向对象的思想,大佬们对I/O多路复用作了一层封装,让使用者不用考虑底层网络接口的细节,只需要关注应用代码的编写。
大佬们为这个模式取名为:Reactor模式,翻译过来的意思为「反应堆」,这个反应堆指的是「对事件反应」,也就是说来一个事件,Reactor就有相对应的反应/响应。
Reactor模式也叫Dispatcher模式
,即I/O多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程/线程。
Reactor模式主要由Reactor和处理资源池这两个核心部分组成,它们所负责的事情如下:
- Reactor负责监听和分发事件,事件类型包含连接事件、读写事件;
- 处理资源池负责处理事件,如read->业务逻辑->send;
(说到这里,我觉得就像是使用go的gin框架编写web程序时,需要在router配置路由一样,这就对应了Reactor的监听事件,但是具体的处理流程就交给handler来处理一样,这种思想具有相似性正是因为这种设计思想在面对复杂逻辑的时候具有更高效的并发处理效率。)
Reactor模式是灵活多变的,在理论上该模式有4种方案的选择:
- 单Reactor单进程/线程;
- 单Reactor多进程/线程;
- 多Reactor单进程/线程;
- 多Reactor多进程/线程;
其中,「多Reactor单进程/线程」实现方案相比与「单Reactor单进程/线程」方案,不仅复杂而且没有性能优势,在实际中没有应用。
省下的3个方案都是比较经典主流的。
方案具体使用进程还是线程,要看使用的编程语言以及平台有关:
- Java语言一般使用线程,比如Netty
- C语言进程和线程都可以,例如Nginx使用的是进程,Memcache使用的是线程。
2、Reactor
2.1、单Reactor单进程/线程
下面是一张「单Reactor单进程」的方案示意图
进程中有Reactor、Acceptor、Handler三个对象:
- Reactor对象的作用是监听和事件分发;
- Acceptor对象的作用是获取连接;
- Handler对象的作用是处理业务;
这里的select、accept、read、send是系统调用函数,dispatch和「业务处理」是需要完成的操作,其中dispatch是分发事件操作。
该方案的流程如下:
- Reactor对象通过select(IO多路复用接口)监听事件,收到事件后通过dispatch进行分发;
- 如果是连接建立事件,则被分发到Acceptor对象进行处理,该对象会调用accept获取连接并创建一个handler对象来处理后续的响应。
- 如果不是,则交由Handler对象响应。
- handler对象通过read->业务处理->send的流程来完成完整的业务流程。
该方案有两个缺点:
- 第一个缺点,因为只有一个进程,无法利用多核CPU的性能;
- 第二个缺点,Handler对象在业务处理时,整个进程无法处理其他连接的事件,如果业务处理耗时过长,那么造成响应的延迟就会更长;
所以单Reactor单进程的方案不适合计算机密集型的场景,只适用于业务处理非常快速的场景。
Redis是由C语言实现的,在6.0版本之间采用的就是这种方案。
2.2、单Reactor多进程/线程
为了弥补单进程/线程的缺点,于是引入了多进程/线程。
我们来看它的不同之处:
- Handler不再去处理具体的业务逻辑,而是只负责数据的接收和发送。
- 具体的业务逻辑通过分配一个字线程Processor去完成,将处理结果返回给Handler对象。
这种方案的优势在于能够充分利用多核CPU的性能,但是引入了多线程就自然带来了线程竞争资源的问题。
我们需要使用互斥锁来保证对共享资源的并发访问问题。
单Reactor模式还有个问题,因为一个Reactor对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。
2.3、多Reactor多进程/线程
为了能够面对瞬间高并发的场景,于是进而引用多Reactor。
它的方案大致步骤是:主线程的MainReactor对象通过select监控连接建立事件,收到事件后通过Acceptor对象获取连接,此时子Reactor会将MainReactor建立的连接加入进select继续监听,在今后的事件发生中,转而调用SubReactor对应的Handler对象来响应。
这种方案实现起来更加的简单:
- 主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理。
- 主线程和子线程的交互简单,只需要把连接传递给子线程,无需等待返回数据。
3、Proactor
Reactor是非阻塞同步网络模式,而Proactor是异步网络模式。
在非阻塞I/O中,read请求在数据未准备完善的时候立刻返回,可以继续往下执行,此时进程不断地轮询内核,直到数据准备好后,内核将数据拷贝到用户缓存区,read调用才可以获取到结果,过程如下:
需要注意的是,这里的最后一次read调用中,等待数据从内核缓冲区拷贝到用户缓冲区的过程是需要等待的,也就是说这个步骤是「同步的」。
因此,无论read和send是阻塞I/O还是非阻塞I/O,都是同步调用。如果内核实现的拷贝效率不高,read调用就会在这个同步过程中等待比较长的时间。
而真正的异步I/O是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程都不需要等待。
当我们发起aio_read
(异步I/O)之后,就立即返回,内核自动将数据从内核空间拷贝到用户空间,这个拷贝过程同样是异步的,应用程序并不需要主动发起拷贝动作。
显而易见的是,异步I/O比同步I/O的性能更好。
Proatcor正是采用了异步I/O技术,所以被称为异步网络模型。
我们再来对比一下Reactor和Proactor的区别:
- Reactor是非阻塞同步网络模式,感知的是就绪可读写事件。在每次感知到有事件发生的时候,就需要应用程序主动调用read方法来完成数据的读取,这个过程是同步的,读取完数据后应用进程才能处理数据。
- Proactor是异步网络模式,感知的是已完成的读写事件。在发起异步读写请求时,需要传入数据缓冲区的地址等信息,系统内核就可以自动帮我们将数据的读写请求工作完成,操作系统完成读写工作后,就会自动通知应用进程直接处理数据。
因此,Reactor可以理解为「来了事件操作系统通知应用进程,让应用进程来处理」,而Proactor可以理解为「来了事件操作系统来处理,处理完再通知应用进程」。
无论是Reactor还是Proactor,都是基于一种「事件分发」的网络编程模式,区别在于Reactor模式是基于「待完成」的I/O事件,而Proactor模式则是基于「已完成」的I/O事件。
Proactor的示意图如下:
Proactor工作流程如下:
- Proactor Initiator负责创建Proactor和Handler对象,并将Proactor和Handler通过Asychronous Operation Processor注册到内核
- Asychronous Operation Processor负责处理注册请求,并处理I/O操作
- Asychronous Operation Processor完成I/O操作后通知Proactor
- Proactor回调对应的Handler进行业务处理
- Handler完成业务处理
可惜的是,在Linux下的异步I/O是不完善的,aio
系列函数是由POSIX定义的异步操作接口,不是真正的操作系统级别的支持,而是在用户空间模拟出来的异步,并且仅支持基于本地文件的aio异步操作,网络编程中的socket是不支持的,这也使得Linux的高性能网络程序都是使用Reactor方案。
Windows里实现了一套完整的支持socket的异步编程接口,叫做IOCP
。由操作系统级别实现的异步I/O,是真正意义上的异步I/O。
4、总结
常见的Reactor实现方案有三中。
第一种是单Reactor单进程/线程,不考虑进程间通信以及数据同步的问题,实现起来简单,但是无法利用多核CPU,而且处理业务逻辑的时间不能太长,所以只适用于业务处理快速的场景,Redis的6.0版本之前采用的是单Reactor单进程的方案。(6.0版本之后是单线程执行 + 多线程 I/O 优化的改进型 Reactor 模型)
第二种方案单Reactor多进程/线程,解决了方案一的缺陷,但是在面对瞬时高并发的场景时,单Reactor会成为性能瓶颈。
第三种方案是多Reactor多进程/线程,主Reactor只负责监听事件,响应事件的工作交给了Reactor,Netty和Memcache都采用了「多Reactor多线程」的方案,Nigin采用了这种模式。
Reactor可以理解为「来了事件操作系统通知应用进程,让应用进程来处理」,而Proactor为「来了事件操作系统来处理,处理完通知进程」。
无论是Proactor还是Reactor,都是一种基于「事件分发」的网络编程模式。
5、参考博客
本篇博客个人学习、总结、摘抄至:[小林coding](9.3 高性能网络模式:Reactor 和 Proactor | 小林coding)
标签:异步,Reactor,进程,高性能,线程,事件,Proactor From: https://www.cnblogs.com/MelonTe/p/18533718