最近学习NIO,看《Netty权威指南》的时候,讲JDK1.5的NIO提到了Reactor模式。
我到网上看了一下,发现Reactor模式一开始在ACE(AdaptiveCommunication Environment是一个跨平台的用于并发通信的C++框架)中提得比较多。用于同步非阻塞的网络通讯,简化了事件驱动程序的开发,允许事件驱动的应用对源自许多不同事件源的事件做出反应,如I/O句柄,定时器,以及信号。
在JDK还没有引入NIO的时候,在网络编程中只能使用BIO(同步阻塞),Server先accept多个Client来的请求,然后分别分配一个Thread来处理一个请求。如Tomcat5.0就是使用这种方式来处理客户端请求(Connector/Processor),只是使用了线程池来进一步优化。
在引入NIO后,通过多路复用器Selector来实现非阻塞的Socket通讯,这时Reactor模式有了用武之地。
在分布式系统尤其是服务器这一类事件驱动应用中,需要处理多个同时到来的请求,而且这些请求具有多个不同的状态,如何才能有序高效地处理这些请求要求我们满足以下几点:
l 为了提高系统的可测量性和反应时间,应用程序不能长时间阻塞在某个事件源上而停止对其他事件的处理,这样会严重降低对客户端的响应度。
l 为了提高吞吐量,任何没有必要的上下文切换、同步和CPU之间的数据移动都要避免。
l 引进新的服务或改良已有的服务都要对既有的事件分离和调度机制带来尽可能小的影响。
l 大量的应用程序代码需要隐藏在复杂的多线程和同步机制之后。
为了解决这些问题,我们需要将不同事件分离出来,不同的事件由不同的Handler来处理,一个Handler只处理一种类型的事件;而且把事件的调度分派和处理分离开来,由一个管理中心统一分配和调度。
Reactor(反应器)就是这个统一的管理中心,负责接收客户请求,分离(demultiplex)不同的事件,并调度(dispatch)给相应的应用程序(Handler)来处理。
在Reactor模式中,有5个关键的参与者:
1. 描述符/操作数(Handle):由操作系统提供,用于识别每一个事件,如Socket描述符、文件描述符等。在Linux中,它用一个整数来表示。事件可以来自外部,如来自客户端的连接请求、数据等。事件也可以来自内部,如定时器事件。
2. 同步事件分离器(Demultiplexer):是一个函数,用来等待一个或多个事件的发生。调用者会被阻塞,直到分离器分离的描述符集上有事件发生。Linux的select函数是一个经常被使用的分离器。
3. 事件处理器接口(EventHandler):是由一个或多个模板函数组成的接口。这些模板函数描述了和应用程序相关的对某个事件的操作。
4. 具体的事件处理器(Handler):是事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。它使用描述符来识别事件、识别应用程序提供的服务。
5. Reactor 管理器(Reactor):定义了一些接口,用于应用程序控制事件调度,以及应用程序注册、删除事件处理器和相关的描述符。它是事件处理器的调度核心。 Reactor管理器使用同步事件分离器来等待事件的发生。一旦事件发生,Reactor管理器先是分离每个事件,然后调度事件处理器,最后调用相关的模板函数来处理这个事件。
通过上述分析,我们注意到,是Reactor管理器而不是应用程序负责等待事件、分离事件和调度事件。实际上,Reactor管理器并没有被具体的事件处理器调用,而是管理器调度具体的事件处理器,由事件处理器对发生的事件做出处理。这就是类似Hollywood原则的“反向控制”。应用程序要做的 仅仅是实现一个具体的事件处理器,然后把它注册到Reactor管理器中。接下来的工作由管理器来完成。
在Java中Reactor模式的实现要结合NIO的Selector。例如在一个服务器对应多个客户端的场景下,先初始化ServerSocketChannel绑定端口并设置成非阻塞模式;然后把ServerSocketChannel注册到Selector中,监听SelectionKey.OP_ACCEPT操作位;最后在run()中循环遍历Selector,取出就绪的Channel分派给响应的Handler处理即可。
public class Reactor implements Runnable { public final Selector selector; public final ServerSocketChannel serverSocketChannel; public Reactor(int port) throws IOException { selector = Selector.open(); serverSocketChannel =ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); serverSocketChannel.socket().bind(inetSocketAddress); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } public void run() { try { while (!Thread.interrupted()) { selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectionKeys.iterator(); while (it.hasNext()) { SelectionKey selectionKey = it.next(); dispatch(selectionKey); selectionKeys.clear(); } } } catch (IOException e) { e.printStackTrace(); } } void dispatch(SelectionKey key) {} }
上面的反应器Reactor,通过分离器Selector来监听不同事件(操作数),然后把监听到的事件分派给相应的Handler处理,充当着管理调度中心的角色。
反应器模式与观察者模式在某些方面极为相似:当一个主体发生改变时,所有依属体都得到通知。只是观察者模式与单个事件源关联,而反应器模式则与多个事件源关联。
参考:《Netty权威指南》和博文http://www.cnblogs.com/hzbook/archive/2012/07/19/2599698.html
原文:http://blog.csdn.net/tiwerbao/article/details/43989751