Reactor 模式

Reactor是一种广泛应用在服务器端开发的设计模式。

那么,Reactor模式究竟是个什么东西呢?这要从事件驱动的开发方式说起。对于应用服务器,一个主要规律就是,CPU的处理速度是要远远快于IO速度的,如果CPU为了IO操作(例如从Socket读取一段数据)而阻塞显然是不划算的。好一点的方法是分为多进程或者线程去进行处理,但是这样会带来一些进程切换的开销,试想一个进程一个数据读了500ms,期间进程切换到它3次,但是CPU却什么都不能干,就这么切换走了,是不是也不划算?

这时先驱们找到了事件驱动,或者叫回调的方式,来完成这件事情。这种方式就是,应用业务向一个中间人注册一个回调(event handler),当IO就绪后,就向这个中间人产生一个事件,并通知此handler进行处理。这种回调的方式,也体现了“好莱坞原则”(Hollywood principle)-“Don’t call us, we’ll call you”,在我们熟悉的IoC中也有用到。看来软件开发真是互通的!

现在来看Reactor模式。在前面事件驱动的例子里有个问题:我们如何知道IO就绪这个事件,谁来充当这个中间人?Reactor模式的答案是:由一个不断等待和循环的单独进程(线程)来做这件事,它接受所有handler的注册,并负责先操作系统查询IO是否就绪,在就绪后就调用指定handler进行处理,这个角色的名字就叫做Reactor。

在reactor模式里,操作系统只负责通知IO就绪,具体的IO操作仍然是要在业务进程里阻塞的去做的。

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上的事件类型。

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接口,实现特定事件处理逻辑。

以读操作为例来看看Reactor中的具体步骤

读取操作:
1应用程序注册读就绪事件和相关联的事件处理器

2 事件分离器等待事件的发生

3当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器

4 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理。

使用同步IO来模拟Reactor模式的流程如下:
1 主线程首先将socket的读就绪事件注册到epoll内核注册表中。

2 主线程调用epoll_wait 等待socket的读就绪事件

3epoll 返回就绪的socket(socket上有数据可读)并通知主线程,主线程将该socket读就绪事件放入到请求队列中。

4 睡眠在请求队列中的某个工作线程被唤醒,处理该socket读就绪事件,处理完成后,将该socket的写就绪事件注册到epoll内核注册表中。

5 主线程调用epoll_wait等待socket的写就绪事件

6 当socket可写时,通知主线程,主线程将写就绪socket放入请求队列

7睡眠在请求队列的工作线程被唤醒,处理该socket写事件。