第一次听到Reactor模式是三年前的某个晚上,一个室友忽然跑过来问我什么是Reactor模式?我上网查了一下,不少人都是给出NIO中的 Selector的例子,并且就是NIO里Selector多路复用模型,只是给它起了一个比较fancy的名字而已,虽然它引入了EventLoop概 念,这对我来讲是新的概念,可是代码实现倒是同样的,于是我并无很在乎这个模式。然而最近开始读Netty源码,而Reactor模式是不少介绍Netty的文章中被大肆宣传的模式,于是我再次问本身,什么是Reactor模式?本文就是对这个问题关于个人一些理解和尝试着来解答。
html
要回答这个问题,首先固然是求助Google或Wikipedia,其中Wikipedia上说:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。从这个描述中,咱们知道Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。若是用图来表达:
从结构上,这有点相似生产者消费者模式,即有一个或多个生产者将事件放入一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事件来处理;而Reactor模式则并无Queue来作缓冲,每当一个Event输入到Service Handler以后,该Service Handler会主动的根据不一样的Event类型将其分发给对应的Request Handler来处理。
更学术的,这篇文章(Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events)上说:“The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. Each service in an application may consistent of several methods and is represented by a separate event handler that is responsible for dispatching service-specific requests. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. Also known as Dispatcher, Notifier”。这段描述和Wikipedia上的描述相似,有多个输入源,有多个不一样的EventHandler(RequestHandler)来处理不一样的请求,Initiation Dispatcher用于管理EventHander,EventHandler首先要注册到Initiation Dispatcher中,而后Initiation Dispatcher根据输入的Event分发给注册的EventHandler;然而Initiation Dispatcher并不监听Event的到来,这个工做交给Synchronous Event Demultiplexer来处理。java
在解决了什么是Reactor模式后,咱们来看看Reactor模式是由什么模块构成。图是一种比较简洁形象的表现方式,于是先上一张图来表达各个模块的名称和他们之间的关系:
Handle:即操做系统中的句柄,是对资源在操做系统层面上的一种抽象,它能够是打开的文件、一个链接(Socket)、Timer等。因为Reactor模式通常使用在网络编程中,于是这里通常指Socket Handle,即一个网络链接(Connection,在Java NIO中的Channel)。这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事件,对ServerSocketChannnel能够是CONNECT事件,对SocketChannel能够是READ、WRITE、CLOSE事件等。
Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到来,若是阻塞等待返回,即表示在返回的Handle中能够不阻塞的执行返回的事件类型。这个模块通常使用操做系统的select来实现。在Java NIO中用Selector来封装,当Selector.select()返回时,能够调用Selector的selectedKeys()方法获取Set<SelectionKey>,一个SelectionKey表达一个有事件发生的Channel以及该Channel上的事件类型。上图的“Synchronous Event Demultiplexer ---notifies--> Handle”的流程若是是对的,那内部实现应该是select()方法在事件到来后会先设置Handle的状态,而后返回。不了解内部实现机制,于是保留原图。
Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还做为Reactor模式的入口调用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event()方法。
Event Handler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。
Concrete Event Handler:事件EventHandler接口,实现特定事件处理逻辑。react
简单描述一下Reactor各个模块之间的交互流程,先从序列图开始:
1. 初始化InitiationDispatcher,并初始化一个Handle到EventHandler的Map。
2. 注册EventHandler到InitiationDispatcher中,每一个EventHandler包含对相应Handle的引用,从而创建Handle到EventHandler的映射(Map)。
3. 调用InitiationDispatcher的handle_events()方法以启动Event Loop。在Event Loop中,调用select()方法(Synchronous Event Demultiplexer)阻塞等待Event发生。
4. 当某个或某些Handle的Event发生后,select()方法返回,InitiationDispatcher根据返回的Handle找到注册的EventHandler,并回调该EventHandler的handle_events()方法。
5. 在EventHandler的handle_events()方法中还能够向InitiationDispatcher中注册新的Eventhandler,好比对AcceptorEventHandler来,当有新的client链接时,它会产生新的EventHandler以处理新的链接,并注册到InitiationDispatcher中。数据库
在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server来分析Reactor模式,这个Logging Server的实现彻底遵循这里对Reactor描述,于是放在这里以作参考。Logging Server中的Reactor模式实现分两个部分:Client链接到Logging Server和Client向Logging Server写Log。于是对它的描述分红这两个步骤。
Client链接到Logging Server
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请求。
Client向Logging Server写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等待下一第二天志写请求。
在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有对Reactor模式的C++的实现版本,多年不用C++,于是略过。 编程
在Java的NIO中,对Reactor模式有无缝的支持,即便用Selector类封装了操做系统提供的Synchronous Event Demultiplexer功能。这个Doug Lea已经在Scalable IO In Java中有很是深刻的解释了,于是再也不赘述,另外这篇文章对Doug Lea的Scalable IO In Java有一些简单解释,至少它的代码格式比Doug Lea的PPT要整洁一些。
须要指出的是,不一样这里使用InitiationDispatcher来管理EventHandler,在Doug Lea的版本中使用SelectionKey中的Attachment来存储对应的EventHandler,于是不须要注册EventHandler这个步骤,或者设置Attachment就是这里的注册。并且在这篇文章中,Doug Lea从单线程的Reactor、Acceptor、Handler实现这个模式出发;演化为将Handler中的处理逻辑多线程化,实现相似Proactor模式,此时全部的IO操做仍是单线程的,于是再演化出一个Main Reactor来处理CONNECT事件(Acceptor),而多个Sub Reactor来处理READ、WRITE等事件(Handler),这些Sub Reactor能够分别再本身的线程中执行,从而IO操做也多线程化。这个最后一个模型正是Netty中使用的模型。而且在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events的9.5 Determine the Number of Initiation Dispatchers in an Application中也有相应的描述。缓存
对EventHandler的定义有两种设计思路:single-method设计和multi-method设计:
A single-method interface:它将Event封装成一个Event Object,EventHandler只定义一个handle_event(Event event)方法。这种设计的好处是有利于扩展,能够后来方便的添加新的Event类型,然而在子类的实现中,须要判断不一样的Event类型而再次扩展成 不一样的处理方法,从这个角度上来讲,它又不利于扩展。另外在Netty3的使用过程当中,因为它不停的建立ChannelEvent类,于是会引发GC的不稳定。
A multi-method interface:这种设计是将不一样的Event类型在 EventHandler中定义相应的方法。这种设计就是Netty4中使用的策略,其中一个目的是避免ChannelEvent建立引发的GC不稳定, 另一个好处是它能够避免在EventHandler实现时判断不一样的Event类型而有不一样的实现,然而这种设计会给扩展新的Event类型时带来很是 大的麻烦,由于它须要该接口。
关于Netty4对Netty3的改进能够参考这里:网络
归功与Netty和Java NIO对Reactor的宣传,本文慕名而学习的Reactor模式,于是已经默认Reactor具备很是优秀的性能,然而慕名归慕名,到这里,我仍是要不得不问本身Reactor模式的好处在哪里?即为何要使用这个Reactor模式?在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中是这么说的:并发
这些貌似是不少模式的共性:解耦、提高复用性、模块化、可移植性、事件驱动、细力度的并发控制等,于是并不能很好的说明什么,特别是它鼓吹的对性能的提高,这里并无体现出来。固然在这篇文章的开头有描述过另外一种直观的实现:Thread-Per-Connection,即传统的实现,提到了这个传统实现的如下问题:
对于性能,它其实就是第一点关于Efficiency的描述,即线程的切换、同步、数据的移动会引发性能问题。也就是说从性能的角度上,它最大的提高就是减小了性能的使用,即不须要每一个Client对应一个线程。个人理解,其余业务逻辑处理不少时候也会用到相同的线程,IO读写操做相对CPU的操做仍是要慢不少,即便Reactor机制中每次读写已经能保证非阻塞读写,这里能够减小一些线程的使用,可是这减小的线程使用对性能有那么大的影响吗?答案貌似是确定的,这篇论文(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)对随着线程的增加带来性能下降作了一个统计:
在这个统计中,每一个线程从磁盘中读8KB数据,每一个线程读同一个文件,于是数据自己是缓存在操做系统内部的,即减小IO的影响;全部线程是事先分配的,不会有线程启动的影响;全部任务在测试内部产生,于是不会有网络的影响。该统计数据运行环境:Linux 2.2.14,2GB内存,4-way 500MHz Pentium III。从图中能够看出,随着线程的增加,吞吐量在线程数为8个左右的时候开始线性降低,而且到64个之后而迅速降低,其相应事件也在线程达到256个后指数上升。即1+1<2,由于线程切换、同步、数据移动会有性能损失,线程数增长到必定数量时,这种性能影响效果会更加明显。
对于这点,还能够参考C10K Problem,用以描述同时有10K个Client发起链接的问题,到2010年的时候已经出现10M Problem了。
固然也有人说:Threads are expensive are no longer valid.在不久的未来可能又会发生不一样的变化,或者这个变化正在、已经发生着?没有作过比较仔细的测试,于是不敢随便断言什么,然而本人观点,即便线程变的影响并无之前那么大,使用Reactor模式,甚至时SEDA模式来减小线程的使用,再加上其余解耦、模块化、提高复用性等优势,仍是值得使用的。
Reactor模式的缺点貌似也是显而易见的:
1. 相比传统的简单模型,Reactor增长了必定的复杂性,于是有必定的门槛,而且不易于调试。
2. Reactor模式须要底层的Synchronous Event Demultiplexer支持,好比Java中的Selector支持,操做系统的select系统调用支持,若是要本身实现Synchronous Event Demultiplexer可能不会有那么高效。
3. Reactor模式在IO读写数据时仍是在同一个线程中实现的,即便使用多个Reactor机制的状况下,那些共享一个Reactor的Channel若是出现一个长时间的数据读写,会影响这个Reactor中其余Channel的相应时间,好比在大文件传输时,IO操做就会影响其余Client的相应时间,于是对这种操做,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。
Reactor Pattern WikiPedia
Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
Scalable IO In Java
C10K Problem WikiPedia