高性能 I/O 设计模式 Reactor编程
一.Reactor模式与Proactor模式比较设计模式
通常I/O模型分为以下三类:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞缓存
(1)同步阻塞安全
在此种方式下,用户进程在发起一个IO操做之后,必须等待IO操做的完成,只有当真正完成了IO操做之后,用户进程才能运行。JAVA传统的IO模型属于此种方式!服务器
(2)同步非阻塞网络
在此种方式下,用户进程发起一个IO操做之后边可返回作其它事情,可是用户进程须要时不时的询问IO操做是否就绪,这就要求用户进程不停的去询问,从而引入没必要要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。多线程
(3)异步阻塞并发
此种方式下是指应用发起一个IO操做之后,不等待内核IO操做的完成,等内核完成IO操做之后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为何说是阻塞的呢?由于此时是经过select系统调用来完成的,而select函数自己的实现方式是阻塞的,而采用select函数有个好处就是它能够同时监听多个文件句柄(若是从UNP的角度看,select属于同步操做。由于select以后,进程还须要读写数据),从而提升系统的并发性!异步
(4)异步非阻塞socket
在此种模式下,用户进程只须要发起一个IO操做而后当即返回,等IO操做真正的完成之后,应用程序会获得IO操做完成的通知,此时用户进程只须要对数据进行处理就行了,不须要进行实际的IO读写操做,由于真正的IO读取或者写入操做已经由内核完成了。目前Java中尚未支持此种IO模型。
注:其实阻塞与非阻塞均可以理解为同步范畴下才有的概念,对于异步,就不会再去分阻塞非阻塞。对于用户进程,接到异步通知后,就直接操做进程用户态空间里的数据好了。
接下来用读操做和写操做来讲明下Reactor模式和Proactor模式的区别
Reactor模式:
读取操做:
1. 应用程序注册读就绪事件和相关联的事件处理器
2. 事件分离器等待事件的发生
3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器
4. 事件处理器首先执行实际的读取操做,而后根据读取到的内容进行进一步的处理
备注:写操做与读取操做相似
Proactor模式:
读取操做:
1. 应用程序初始化一个异步读取操做,而后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。
2. 事件分离器等待读取操做完成事件
3. 在事件分离器等待读取操做完成的时候,操做系统调用内核线程完成读取操做(异步IO都是操做系统负责将数据读写到应用传递进来的缓冲区供应用程序操做,操做系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序须要传递缓存区。
4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不须要进行实际的读取操做。
备注:Proactor中写入操做和读取操做,只不过感兴趣的事件是写入完成事件。
从上总结以下:
(1)Reactor 和 Proactor 是基于事件驱动
(2)Reactor和Proactor模式的主要区别就是真正的读取和写入操做是有Who来完成的,Reactor中须要应用程序本身读取或者写入数据,而Proactor模式中,应用程序不须要进行实际的读写过程,它只须要从缓存区读取或者写入便可,操做系统会读取缓存区或者写入缓存区到真正的IO设备.
二.Reactor模式
Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。若是用图来表达:
从结构上,这有点相似生产者消费者模式,Reactor模式每当一个Event输入到Service Handler以后,该Service Handler会主动的根据不一样的Event类型将其分发给对应的Request Handler来处理。
2.1 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接口,实现特定事件处理逻辑。
2.2 业务流程及时序图
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模式三种实现模式
3.1 Reactor模式的第一种实现模式(单线程模式)
因为Reactor模式使用的是异步非阻塞IO,全部的IO操做都不会被阻塞,理论上一个线程能够独立处理全部的IO操做。包括一个线程池处理connect事件(Acceptor),一个线程池处理Read事件,一个线程池处理Write事件,虽然Reactor Thread是单线程的,可是从Reactor Thread到Handler都是异步的,从而I/O都是多线程化的。
对于一些小容量应用场景,可使用到单线程模型。但对于高负载,大并发的应用却不合适,主要缘由以下:
1)、当一个NIO线程同时处理成百上千的链路,性能上没法支撑,即便NIO线程的CPU负荷达到100%,也没法彻底处理消息
2)、当NIO线程负载太重后,处理速度会变慢,会致使大量客户端链接超时,超时以后每每会重发,更加剧了NIO线程的负载。
3)、可靠性低,一个线程意外死循环,会致使整个通讯系统不可用
因为Reactor单线程模式有以上的缺点,因此就有了下面的Reactor多线程模式
3.2 Reactor模式的第二种实现模式(多线程模式)
如上所示,经过Reactor Thread Pool来提升Event的分发能力,在绝大多数场景下,该模型都能知足性能需求。可是,在一些特殊的应用场景下,如服务器会对客户端的握手消息进行安全认证。这类场景下,单独的一个Acceptor线程可能会存在性能不足的问题。
3.3 Reactor模式的第三种实现模式(主从模式)
与Reactor多线程模式不一样之处,主从模式有两个,mainReactor主要负责监听Server Socket、Accept链接以及安全认证,而且将创建好的socket分派给subReactor,subReactor负责多路分离已链接的socket,读写网络数据,对业务处理功能,其扔给工程线程池(worker)完成。通常状况下,subReactor个数能够与CPU个数相同。
四.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模式。