Reactor模型
1.IO多路复用模型
“多路“是指多个网络连接,”复用“是指复用同一个线程。采用IO多路复用可以让单个线程高效的处理多个连接请求。
客户端select循环询问服务端是否可以发起read,这个过程是阻塞的,当服务端准备好数据告诉客户端readable,客户端即可发起read请求,此时因为服务端已经准备好了数据,直接返回。整个过程只在调用select/poll/epoll时才会阻塞,收发客户消息是不会阻塞的。具体流程如下:
2.Reactor模型
Reactor模型是基于IO多路复用的同步IO。多个客户端并发提出请求,当请求抵达后,服务处理程序使用IO多路复用策略,同步的派发这些请求到相应的请求处理程序。
2.1角色构成
-
Handle(句柄):本质上表示一种资源,该资源表示一个个事件,事件既可以来自于外部,也可以来自内部。
外部事件比如客户端的连接请求,客户端发送过来的数据等;内部事件比如操作系统产生的定时事件等。
-
Synchronous Event Demultiplexer(同步事件分离器):是一个系统调用,用于等待事件的发生。调用方在调用它的时候会被阻塞,一直阻塞到同步事件分离器上有事件产生为止。
对于Linux来说,同步事件分离器就是select,poll,epoll等;对于Java NIO来说,同步事件分离器对应的组件就是Selector,对应的阻塞方法就是select方法。
-
Event Handler(事件处理器):本身由多个回调方法构成,这些回调方法构成了与应用相关的对于某个事件的反馈机制。Java NIO中没有提供事件处理机制,自己编写代码完成。Netty中提供了大量的回调方法,供我们在特定事件产生时实现相应的回调方法进行业务逻辑处理,既
ChannelHandler
,ChannelHandler
中的方法对应的都是一个个事件的回调。 -
Initiation Dispatcher(初始分发器):就是Reactor角色。
Initiation Dispatcher
(初始分发器)通过Synchronous Event Demultiplexer
(同步事件分离器)来等待事件发生,一旦事件发生,Initiation Dispatcher
(初始分发器)会分离出每一个事件,然后调用Event Handler
(事件处理器),最后调用相关的ChannelHandler
(回调方法)来处理这些事件。Netty中
ChannelHandler
中的一个个回调方法都是由bossGroup
或workGroup
中的某个EventLoop来调用的。
2.2 单线程Reactor模型
三个主要的角色:Reactor(派发者),acceptor(接收者),dispatch(调度)。
单线程模型中所有的IO操作都在一个线程上完成。IO操作包括accept(),read(),write(),connect()等。
- 服务器中Reactor是一个线程对象,该线程会启动事件循环,并使用Selector实现IO多路复用。注册一个Acceptor事件处理器到Reactor中,Acceptor事件处理器所关注的事件是ACCEPT事件,这样Reactor会监听客户端向服务器端发起的连接请求事件,也就是ACCEPT事件。
- 客户端向服务端发起一个连接请求,Reactor监听到该ACCEPT事件的发生并将该事件派发给相应的Accpetor处理器进行处理。Acceptor处理器通过accpet()方法得到与这个客户端对应的连接(SocketChannel),然后将该连接所关注的READ事件以及对应的READ事件处理器注册到Reactor中,这样Reactor就会监听该连接的READ事件了。当服务器需要向客户端发送数据时,就向Reactor注册该连接的WRITE事件和其处理器。
- 当Reactor监听到有READ事件发生时,将相关的事件派发给对应的处理器进行处理。比如,READ处理器会通过SocketChannel的read()方法读取数据,此时read()操作可以直接读取到数据,不会阻塞等待可读的数据到来。
- 当处理完所有就绪的感兴趣的IO事件后,Reactor线程会再次执行select()阻塞等待新的事件就绪并将其分派给对应的处理器进行处理。
2.2 工作者线程模型(单Reactor多线程模型)
添加一个工作者线程池,并将非IO操作从Reactor线程中移出转交给工作线程池来执行,这样可以提高Reactor线程的IO响应。
2.3 多Reactor线程模型
Reactor线程池中的每一个Reactor线程都会有自己的Selector。
mainReactor可以只有一个,subReactor一般有多个,数量默认为服务器处理器数+1。
mainReactor线程主要负责接收客户端的连接请求,然后将接收到的SocketChannel传递给subReactor,由sub完成和客户端的通信。
Accpetor事件处理器注册到mainReactor中,当有连接请求时,mainReactor监听到该ACCEPT事件并将该事件派发给Acceptor事件处理器进行处理。Acceptor处理器通过accept()方法得到与这个客户端对应的连接(SocketChannel),然后将这个SocketChannel传递给subReactor线程池。
多Reactor线程模型将”接收客户端的连接请求(accept操作)“和与该客户端的通信(read,write,connect操作)”分在两个Reactor来完成。这样就不会因为read()数据量太大而导致后面的客户端连接请求得不到及时处理。多核操作系统中提升应用负载和吞吐量。
3.Netty中的Reactor模型
Netty服务端使用多Reactor线程模型。
NioEventLoop——>Reactor
Selector——>Synchronous EventDemultuplexer
ChannelHandler——>Event Handler
ConcreteEventHandler——>具体的ChannelHandler的实现
ServerBootstrapAcceptor——>acceptor
- 服务端程序启动时,配置ChannelPipeline(其中是ChannelHandler链,所有的事件发生时都会触发事件处理器ChannelHandler中的某个方法,这个事件会在ChannelPipeline中的ChannelHandler链中传播),然后从bossGroup中获取一个NioEventLoop实现服务端服务端程序绑定本地端口的操作,将对应的ServerSocketChannel注册到该NioEventLoop中的Selector上,并注册ACCEPT事件为ServerSocketChannel所感兴趣的事件;
- NioEventLoop事件循环启动,此时开始监听客户端的连接请求;
- 当有客户端向服务器端发起连接请求时,NioEventLoop的事件循环监听到该ACCEPT事件,通过accept()方法得到与这个客户端的连接(SocketChannel),然后触发channelRead事件(回调ChannelHandle中的channelRead()方法),该事件会在ChannelPipeline中的ChannelHandle链中执行,传播;
- ServerBootstrapAcceptor的readChannel()方法会将SocketChannel(客户端的连接)注册到workerGroup中的某个NioEventLoop的Selector上,并注册READ事件为SocketChannel所感兴趣的事件。
- 启动SocketChannel所在NioEventLoop的事件循环,接下来就可以开始客户端和服务器端的通信了。
4.Redis中的Reactor模型
Redis中实现了单线程的Reactor模型。
Redis单线程的主循环中统一处理文件事件和时间事件,信号事件由专门的handler来处理。
文件事件可以理解为IO事件。将产生的文件事件Socket放入一个队列中,然后依次分派给文件事件处理器。
- Socket
- IO多路复用程序:可以类比为Java NIO中的selector;
- 文件事件分派器:根据不同的事件,选择不同的事件处理器进行处理;
- 事件处理器:具体的事件处理器。