Reactor的中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有I/O事件到来或是准备就绪,多路复用器返回,并将事先注册的相应I/O事件分发到对应的处理器中
相关概念介绍:
- 事件:就是状态;比如:读就绪事件指的是我们可以从内核读取数据的状态
- 事件分离器:一般会把事件的等待发生交给epoll、select;而事件的到来是随机,异步的,所以需要循环调用epoll,在框架里对应封装起来的模块就是事件分离器(简单理解为对epoll封装)
- 事件处理器:事件发生后需要进程或线程去处理,这个处理者就是事件处理器,一般和事件分离器是不同的线程
Reactor的一般流程
- 1)应用程序在事件分离器注册读写就绪事件和读写就绪事件处理器
- 2)事件分离器等待读写就绪事件发生
- 3)读写就绪事件发生,激活事件分离器,分离器调用读写就绪事件处理器
- 4)事件处理器先从内核把数据读取到用户空间,然后再处理数据
在Reactor线程模型的发展过程中,出现了不同的实现方式,具体有:
-
单Reactor单线程
-
单Reactor多线程
-
主从Reactor多线程
单Reactor + 单线程 (Reactor和handler都在同一个线程中)
方案说明:
- Select是IO复用模型介绍的标准网络编程API,可以实现应用程序通过一个阻塞对象监听多路连接请求
- Reactor对象通过Select监控客户端请求事件,收到事件后通过Dispatch进行分发
- 如果是建立连接请求事件,则由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的后续业务
- 如果不是建立连接事件,则Reactor会分发调用连接对应的Handler来处理业务
- Handler会完成Read->业务处理->Send的完整业务流程
这种模式的缺点在于:
- 服务器端用一个线程通过多路复用搞定所有的IO操作,包括连接、读、写等,但是如果客户端连接数较多,将无法支撑,比如处理一个客户端的业务时,别的客户端的业务请求只能阻塞等待
- 单线程无法发挥多核CPU的性能
单Reactor + 多线程
方案说明:
- Reactor对象通过select监控客户端请求事件,收到事件后,通过dispatcher进行分发
- 如果是建立连接的请求,则由Acceptor通过accept处理连接请求,然后创建一个Handler对象处理完成连接后的各种事件
- 如果不是连接请求,则由Reactor分发调用连接对应的handler进行处理
- handler只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的worker线程池的某个线程处理业务
- worker线程池会分发独立的线程完成真正的业务,并将结果返回给handler
- handler收到响应的结果后,再通过send将结果返回给client
优点:
-
多线程可以充分利用多核CPU的处理能力
-
采用线程池复用线程,减少创建和销毁线程带来的性能开销
缺点:
-
reactor处理所有事件的监听和响应,在单线程运行,高并发场景下容易出现性能瓶颈
-
多线程数据共享和访问比较复杂
主从Reactor + 多线程
方案说明:
- Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
- 当Acceptor处理连接事件后,MainReactor将连接分配给SubReactor
- SubReactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理
- 当有新事件发生时,SubReactor就会调用对应的handler进行处理
- handler通过Read读取数据,分发给后面的worker线程处理
- worker线程池会分配独立的worker线程进行业务处理,并返回结果
- handler收到响应的结果后,再通过send将结果返回给client
- MainReactor主线程可以关联多个SubReactor子线程
优点:
- 主线程与子线程的数据交互简单职责明确,主线程只需要接收新连接,子线程完成后续的业务处理
- 可以通过扩展多个Reactor子线程的方式来减小单个子线程的压力,提高并发处理能力
Netty就是在主从Reactor多线程模型的基础上进行了一定的改进,同时,Kafka的网络架构设计也采用了这种主从Reactor多线程的模型。
作者:潜行前行
链接:https://juejin.cn/post/6892687008552976398
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。