Reactor模式解析

前言

即将结束的2018年,准备迎接全新的2019年,在此祝大家新年快乐、工作事事顺心、更上一层楼。在2018年里,去了想去的城市,找到了一份心仪的工作,坚持做自己想做的事情。在这里想真诚的和自己说:在2018年里,你已经做的很好了,希望能在新的一年2019,再接再厉。

前一段时间里,一直都在研究Netty3源码,学习代码设计中的高内聚、低耦合,复用和模块化。将精华如何应用到工作上。 以下属于理论性,后期会通过实际代码和理论相结合进行分析。

Reactor 模式

事件驱动的模式,由主动变被动,原来是自己主动去询问事件是否准备就绪,或是如果未就绪就阻塞线程。还有一种是被动的,当有就绪的事件那么就主动通知我。

什么是事件?

事件可以理解为,我们平时所说的将要发生或者正在发送的事情,所具有:不知道什么时候发生,但一定会发生,具有未知性和确定性。

下面会结合Netty去理解Reactor模式在其中的应用

Reactor 简单结构

Reactor模式解析_第1张图片

图-reactor_simple

输入多个事件源,通过多路复用器进行对事件监听,当事件就绪时就会根据事件类型分发给不同的Request Handler(Event Handler)进行处理。

其中 Service Handler由 Initiation Dispatcher(Event Handler容器) 和 Synchronous Event Demultiplexer(多路复用器) 、Handle(句柄 可以是文件、socket、nio channel)组成

Initiation Dispatcher 注册Event Handler 进行对Event Handler管理的,会调用多路复用器监听事件,然后根据事件类型调用对应的Event Handler进行处理

Synchronous Event Demultiplexer Nio中通过封装成Selector对象(具体需要调用操系统的selector) 实现监听多个Channel,至少一个Channel就绪,selector()不再阻塞

Reactor 模式架构

Reactor模式解析_第2张图片

图-reactor_architecture

1、Handle 句柄,是在操作系统层面上进行对资源的抽象,例如打开的文件、Socket、nio Channel 都属于句柄。句柄会对应多种事件,并将来都会发生的。这个Handle会注册到选择复用器上。 例如 Channel就会有 ACCEPT(有新客户端连接事件) CONNECT(客户端连接成功事件) READ(Channel中有可读事件) WRITE(Channel可写事件)

2、Initiation Dispatcher 负责注册Event Handler,并进行对其管理,并将其句柄委托给复用器进行监听(阻塞在select()方法上),当Channel准备就绪则根据事件类型dispatch 到对应的具体实现的Event Handler进行处理

3、Synchronous Event Demultiplexer 多路复用器,在 java nio 使用 Selector来实现的,对注册到selector中的Channel进行监听,一直阻塞在select方法上,当操作系统层面监听到某几个Channel就绪了就会唤醒select线程 selector.selectedKey() 返回 Set 就绪的Key 一个SelectionKey里面存放就绪的Channel 和 对应事件类型。 在select方法中,在Channel就绪时会设置其状态。

4、Event Handler 事件处理接口,定义其方法供给Initiation Dispatcher回调使用

5、Concrete Event Handler 具体事件处理逻辑

Rector 模块与模块之间交互

Reactor模式解析_第3张图片

图-reactor_sequence

1、初始化Initiation Dispatcher 并初始化一个 Map map

2、注册EventHandler 到 Initiation Dispatcher ,而每个EventHandler包含Handle,从而建立起Handle和EventHandler映射关系

3、调用 Initiation Dispatcher 的handle_events方法 启动Event Loop。在Event Loop中,调用select()方法(Synchronous Event Demultiplexer)阻塞等待Event发生

4、当某个Event发生时,select方法返回 SelectionKey(就绪的Channel和对应事件类型),InitiationDispatcher会根据Handle获取对应的EventHandler,并调用其 handle_event()方法

5、在处理事件逻辑方法 handle_event方法中可以继续调用InitiationDispatcher注册新的事件处理器,比如对AcceptorEventHandler来说,当有新的client连接时,它会产生新的EventHandler以处理新的连接,并注册到InitiationDispatcher中

Reactor 模式实现

Reactor模式解析_第4张图片

图-reactor_LoggingServer_connect

  1. Logging Server注册LoggingAcceptor到InitiationDispatcher。
  2. Logging Server调用InitiationDispatcher的handle_events()方法启动。
  3. InitiationDispatcher内部调用select()方法(Synchronous Event Demultiplexer),阻塞等待Client连接。
  4. Client连接到Logging Server。
  5. InitiationDisptcher中的select()方法返回,并通知LoggingAcceptor有新的连接到来。
  6. LoggingAcceptor调用accept方法accept这个新连接。
  7. LoggingAcceptor创建新的LoggingHandler。
  8. 新的LoggingHandler注册到InitiationDispatcher中(同时也注册到Synchonous Event Demultiplexer中),等待Client发起写log请求。

Reactor模式解析_第5张图片

图-reactor_LoggingServer_log

  1. Client发送log到Logging server。
  2. InitiationDispatcher监测到相应的Handle中有事件发生,返回阻塞等待,根据返回的Handle找到LoggingHandler,并回调LoggingHandler中的handle_event()方法。
  3. LoggingHandler中的handle_event()方法中读取Handle中的log信息。
  4. 将接收到的log写入到日志文件、数据库等设备中。
    3.4步骤循环直到当前日志处理完成。
  5. 返回到InitiationDispatcher等待下一次日志写请求。

Java NIO 对Reactor的实现

在selector.register(channel,selector,eventhandler) 通过 SelectionKey的attachment()就可以获取对应就绪Channel的EventHandler,无需在InitiationDispatcher中使用register方法。 从上面可以看出从事件事件等待到就绪、最后到handler进行处理都是单线程的,在处理的过程中会阻塞其他已经就绪的Channel,例如当有大量客户进行连接时,就会影响其他已经连接上正准备读或写的Channel。

优化线程模型:采用Main Reactor和Sub Reator 两种模式, Main Reactor处理客户端的CONNECT事件 和Server端的ACCEPT事件,将accept 新Channel交给Sub Reactor进行监听READ WRITE事件,其中Sub Reactor中会有多个线程(多个Reactor模式)进行执行 selector阻塞 -> selector返回 -> Handler处理事件 操作。 这种正是Netty使用的线程模型。

EventHandler接口定义

A single-method interface:handle_event(ChannelEvent event) 在Netty3 中抽象了一个ChannelEvent,各种事件进行对其实现 便于扩展,例如 MessageEvent(消息事件) ChannelStateEvent(状态事件) ExceptionEvent(异常事件),判断根据不同的ChannelEvent类型再次扩展成不同的处理方法,但是从此角度看它又不利于扩展。 但是有个问题就是,在其处理过程会不断的产生ChannelEvent对象,导致GC不稳定。

A multi-method interface:在接口中定义多个事件方法 channelConnected() channelBound() 等方法,这种设计是Netty4 使用的,可以避免在具体EventHandler中会根据不同事件类型有不同实现。 同样也避免了产生大量ChannelEvent对象引起GC不稳定,但是当需要增加一个事件类型时会带来很大麻烦,因为需要该接口。

Reactor模式好处

解耦、提升复用性、模块化、可移植性、事件驱动、细力度的并发控制(多个线程共同处理Channel事件)

相比较传统阻塞型的网络I/O,一个连接一个线程来说NIO会使用更少的线程,而过多创建线程会增加线程上下文切换、数据同步、数据移动,同样也会占用很大内存(一般一个线程256K),这会大大降低了系统性能

Reactor模式缺点

1、有一定复杂性,不易调试

2、需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持

3、读和写数据时还是在同一个线程中,哪怕使用多个Reactor机制,如果在同个Reactor中Channel出现了长时间的读写,例如大文件传输时,在EventHandler处理比较耗时操作时,就会影响其他Client的响应时间,因而选择Per Channel ,Per Thread或许是更好的选择,或使用Proactor模式(在I/O 还是使用Reactor模式,但是在Handler处理逻辑采用多线程进行实现 这个在Netty4 就会实现,可以自定义一个EventExecutor来进行对Handler逻辑处理)

你可能感兴趣的:(Netty原理剖析,Netty内部实现原理剖析)