彻底搞懂Reactor模型和Proactor模型

在高性能的I/O设计中,有两个著名的模型:Reactor模型和Proactor模型,其中Reactor模型用于同步I/O,而Proactor模型运用于异步I/O操作 。
想要了解两种模型,需要了解一些IO、同步异步的基础知识,彻底搞懂JAVA的网络IO
服务端的线程模型无论是Reactor模型还是Proactor模型,对于支持多连接的服务器,一般可以总结为2种fd和3种事件,如下图:
 

彻底搞懂Reactor模型和Proactor模型

文章插图
 
 
2种fd
  1. listenfd:一般情况,只有一个 。用来监听一个特定的端口(如80) 。
  2. connfd:每个连接都有一个connfd 。用来收发数据 。
3种事件
  1. listenfd进行accept阻塞监听,创建一个connfd
  2. 用户态/内核态copy数据 。每个connfd对应着2个应用缓冲区:readbuf和writebuf 。
  3. 处理connfd发来的数据 。业务逻辑处理,准备response到writebuf 。
Reactor模型无论是C++还是Java编写的网络框架,大多数都是基于Reactor模型进行设计和开发,Reactor模型基于事件驱动,特别适合处理海量的I/O事件 。
Reactor模型中定义的三种角色:
  • Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler 。新的事件包含连接建立就绪、读就绪、写就绪等 。
  • Acceptor:处理客户端新连接,并分派请求到处理器链中 。
  • Handler:将自身与事件绑定,执行非阻塞读/写任务,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel 。可用资源池来管理 。
Reactor处理请求的流程:
读取操作:
  1. 应用程序注册读就绪事件和相关联的事件处理器
  2. 事件分离器等待事件的发生
  3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器
写入操作类似于读取操作,只不过第一步注册的是写就绪事件 。
1.单Reactor单线程模型
Reactor线程负责多路分离套接字,accept新连接,并分派请求到handler 。redis使用单Reactor单进程的模型 。
 
彻底搞懂Reactor模型和Proactor模型

文章插图
 
 
消息处理流程:
  1. Reactor对象通过select监控连接事件,收到事件后通过dispatch进行转发 。
  2. 如果是连接建立的事件,则由acceptor接受连接,并创建handler处理后续事件 。
  3. 如果不是建立连接事件,则Reactor会分发调用Handler来响应 。
  4. handler会完成read->业务处理->send的完整业务流程 。
单Reactor单线程模型只是在代码上进行了组件的区分,但是整体操作还是单线程,不能充分利用硬件资源 。handler业务处理部分没有异步 。
对于一些小容量应用场景,可以使用单Reactor单线程模型 。但是对于高负载、大并发的应用场景却不合适,主要原因如下:
  1. 即便Reactor线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送 。
  2. 当Reactor线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重Reactor线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈 。
  3. 一旦Reactor线程意外中断或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障 。
为了解决这些问题,演进出单Reactor多线程模型 。
2.单Reactor多线程模型
该模型在事件处理器(Handler)部分采用了多线程(线程池) 。
 
彻底搞懂Reactor模型和Proactor模型

文章插图
 
 
消息处理流程:
  1. Reactor对象通过Select监控客户端请求事件,收到事件后通过dispatch进行分发 。
  2. 如果是建立连接请求事件,则由acceptor通过accept处理连接请求,然后创建一个Handler对象处理连接完成后续的各种事件 。
  3. 如果不是建立连接事件,则Reactor会分发调用连接对应的Handler来响应 。
  4. Handler只负责响应事件,不做具体业务处理,通过Read读取数据后,会分发给后面的Worker线程池进行业务处理 。
  5. Worker线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给Handler进行处理 。
  6. Handler收到响应结果后通过send将响应结果返回给Client 。
相对于第一种模型来说,在处理业务逻辑,也就是获取到IO的读写事件之后,交由线程池来处理,handler收到响应后通过send将响应结果返回给客户端 。这样可以降低Reactor的性能开销,从而更专注的做事件分发工作了,提升整个应用的吞吐 。


推荐阅读