什么是Reactor模式
要回答这个问题,首先当然是求助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。如果用图来表达:
更学术的,这篇文章(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来处理。
经典的图
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表达一个有事件发生的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接口,实现特定事件处理逻辑。
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模式包含四部分,分别是Handle(对于系统资源的一种抽象,在redis中就是监听描述符或者是连接描述符)、Synchronous Event Demultiplexer(同步事件分离器,在redis中对应于IO多路复用程序)、Event Handler(事件处理器,在redis中对应于连接应答处理器、命令请求处理器以及命令回复处理器、事件处理器等)和Initiation Dispatcher(事件分派器,在redis中对应于ae.c/aeProcessEvents函数)。
在redis中将感兴趣的事件及类型(读、写)通过IO多路复用程序注册到内核中并监听每个事件是否发生。当IO多路复用程序返回的时候,如果有事件发生,redis在封装IO多路复用程序时,将所有已经发生的事件及该事件的类型封装为aeFiredEvent类型,放到aeEventLoop的fired成员中,形成一个队列。通过这个队列,redis以有序、同步、每次一个套接字事件的方式向文件事件分派器传送套接字,并处理发生的文件事件。redis处理事件(无论是文件事件还是时间事件)都是以原子的方式进行的,中间不存在事件之间的抢占。这很容易理解,redis是单线程模型,不存在处理上的并发操作。
最后需要说明的是redis首先处理发生的文件事件,然后才会处理时间事件,这点我们在介绍redis源码aeProcessEvents的时候会详细注释和介绍
优点 相对而言,Reactor模式又有什么优点呢。论文总结了多方面的优点:解耦、提升复用性、模块化、可移植性、事件驱动、细力度的并发控制等,这里就不一一详细赘述。那么它有没有缺点呢?也是有的,论文最后总结了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模式