Reactor模式和NIO

本文可看成是对Doug Lea Scalable IO in Java一文的翻译。

当前分布式计算 Web Services盛行天下,这些网络服务的底层都离不开对socket的操作。他们都有一个共同的结构:

1. Read request

2. Decode request

3. Process service

4. Encode reply

5. Send reply

经典的网络服务的设计例如以下图,在每一个线程中完毕对数据的处理:

但这样的模式在用户负载添加时,性能将下降非常的快。我们须要又一次寻找一个新的方案,保持数据处理的流畅,非常显然,事件触发机制是最好的解决的方法,当有事件发生时,会触动handler,然后開始数据的处理。

Reactor模式类似于AWT中的Event处理:

Reactor模式參与者

1.Reactor 负责响应IO事件,一旦发生,广播发送给对应的Handler去处理,这类似于AWT的thread

2.Handler 是负责非阻塞行为,类似于AWT ActionListeners;同一时候负责将handlers与event事件绑定,类似于AWT addActionListener

如图:

Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们,在此我们实现事件和handler的绑定。

我们来看看Reactor模式代码:



public class Reactor implements Runnable{

  final Selector selector;

  final ServerSocketChannel serverSocket;

  Reactor(int port) throws IOException {

    selector = Selector.open();

    serverSocket = ServerSocketChannel.open();

    InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),port);

    serverSocket.socket().bind(address);



    serverSocket.configureBlocking(false);

    //向selector注冊该channel

     SelectionKey sk =serverSocket.register(selector,SelectionKey.OP_ACCEPT);



    logger.debug("-->Start serverSocket.register!");



    //利用sk的attache功能绑定Acceptor 假设有事情,触发Acceptor

    sk.attach(new Acceptor());

    logger.debug("-->attach(new Acceptor()!");

  }

  public void run() { // normally in a new Thread

    try {

    while (!Thread.interrupted())

    {

      selector.select();

      Set selected = selector.selectedKeys();

      Iterator it = selected.iterator();

      //Selector假设发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。

      while (it.hasNext())

        //来一个事件 第一次触发一个accepter线程

        //以后触发SocketReadHandler

        dispatch((SelectionKey)(it.next()));

        selected.clear();

      }

    }catch (IOException ex) {

        logger.debug("reactor stop!"+ex);

    }

  }

  //执行Acceptor或SocketReadHandler

  void dispatch(SelectionKey k) {

    Runnable r = (Runnable)(k.attachment());

    if (r != null){

      // r.run();

    }

  }

  class Acceptor implements Runnable { // inner

    public void run() {

    try {

      logger.debug("-->ready for accept!");

      SocketChannel c = serverSocket.accept();

      if (c != null)

        //调用Handler来处理channel

        new SocketReadHandler(selector, c);

      }

    catch(IOException ex) {

      logger.debug("accept stop!"+ex);

    }

    }

  }

}

以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起,当发生事件时,能够马上触发对应链接的Handler。

再看看Handler代码:

public class SocketReadHandler implements Runnable {

  public static Logger logger = Logger.getLogger(SocketReadHandler.class);

  private Test test=new Test();

  final SocketChannel socket;

  final SelectionKey sk;



   static final int READING = 0, SENDING = 1;

  int state = READING;

  public SocketReadHandler(Selector sel, SocketChannel c)

    throws IOException {

    socket = c;

    socket.configureBlocking(false);

     sk = socket.register(sel, 0);

    //将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。

    //參看dispatch(SelectionKey k)

    sk.attach(this);



    //同一时候将SelectionKey标记为可读,以便读取。

    sk.interestOps(SelectionKey.OP_READ);

    sel.wakeup();

  }

  public void run() {

    try{

    // test.read(socket,input);

      readRequest() ;

    }catch(Exception ex){

    logger.debug("readRequest error"+ex);

    }

  }

/**

* 处理读取data

* @param key

* @throws Exception

*/

private void readRequest() throws Exception {

  ByteBuffer input = ByteBuffer.allocate(1024);

  input.clear();

  try{

    int bytesRead = socket.read(input);

    ......

    //激活线程池 处理这些request

    requestHandle(new Request(socket,btt));

    .....

  }catch(Exception e) {

  }



}

注意在Handler里面又运行了一次attach,这样,覆盖前面的Acceptor,下次该Handler又有READ事件发生时,将直接触发Handler.从而開始了数据的读 处理 写 发出等流程处理。

将数据读出后,能够将这些数据处理线程做成一个线程池,这样,数据读出后,马上扔到线程池中,这样加速处理速度:

更进一步,我们能够使用多个Selector分别处理连接和读事件。

一个高性能的Java网络服务机制就要形成,激动人心的集群并行计算即将实现。

两种I/O多路复用模式:Reactor和Proactor

一般地,I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer)。分离器对象可将来自事件源的I/O事件分离出来,并分发到相应的read/write事件处理器(Event Handler)。开发者预先注冊须要处理的事件及其事件处理器(或回调函数);事件分离器负责将请求事件传递给事件处理器。两个与事件分离器有关的模式是Reactor和Proactor。Reactor模式採用同步IO,而Proactor採用异步IO。

 在Reactor中,事件分离器负责等待文件描写叙述符或socket为读写操作准备就绪,然后将就绪事件传递给相应的处理器,最后由处理器负责完毕实际的读写工作。

 而在Proactor模式中,处理器--或者兼任处理器的事件分离器,仅仅负责发起异步读写操作。IO操作本身由操作系统来完毕。传递给操作系统的參数须要包含用户定义的数据缓冲区地址和数据大小,操作系统才干从中得到写出操作所需数据,或写入从socket读到的数据。事件分离器捕获IO操作完毕事件,然后将事件传递给相应处理器。比方,在windows上,处理器发起一个异步IO操作,再由事件分离器等待IOCompletion事件。典型的异步模式实现,都建立在操作系统支持异步API的基础之上,我们将这样的实现称为“系统级”异步或“真”异步,由于应用程序全然依赖操作系统运行真正的IO工作。

 举个样例,将有助于理解Reactor与Proactor二者的差异,以读操作为例(类操作类似)。

 在Reactor中实现读

 - 注冊读就绪事件和对应的事件处理器

 - 事件分离器等待事件

 - 事件到来,激活分离器,分离器调用事件相应的处理器。

 - 事件处理器完毕实际的读操作,处理读到的数据,注冊新的事件,然后返还控制权。

 与例如以下Proactor(真异步)中的读过程比較:

 - 处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这样的情况下,处理器无视IO就绪事件,它关注的是完毕事件。

 - 事件分离器等待操作完毕事件

 - 在分离器等待过程中,操作系统利用并行的内核线程运行实际的读操作,并将结果数据存入用户自己定义缓冲区,最后通知事件分离器读操作完毕。

 - 事件分离器呼唤处理器。

 - 事件处理器处理用户自己定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分离器。

 

实践现状 

 由Douglas Schmidt等人开发的开源C++开发框架ACE,提供了大量与平台无关,支持并发的底层类(线程,相互排斥量等),且在高抽象层次上,提供了两组不同的类--ACE Reactor和ACE Proactor的实现。只是,尽管二者都与平台无关,提供的接口却各异。

 ACE Proactor在windows平台上具有更为优异的性能表现,由于windows在操作系统提供了高效的异步API支持(见http://msdn2.microsoft.com/en-us/library/aa365198.aspx)。

 然而,并不是全部的操作系统都在系统级大力支持异步。像非常多Unix系统就没做到。因此,在Unix上,选择ACE Reactor解决方式可能更好。但这样一来,为了获得最好的性能,网络应用的开发者必须为不同的操作系统维护多份代码:windows上以ACE Proactor为基础,而Unix系统上则採用ACE Reactor解决方式。

 

改进方案

  在这部分,我们将尝试应对为Proactor和Reactor模式建立可移植框架的挑战。在改进方案中,我们将Reactor原来位于事件处理器内的read/write操作移至分离器(最好还是将这个思路称为“模拟异步”),以此寻求将Reactor多路同步IO转化为模拟异步IO。以读操作为样例,改进步骤例如以下:

  - 注冊读就绪事件及其处理器,并为分离器提供数据缓冲区地址,须要读取数据量等信息。

  - 分离器等待事件(如在select()上等待)

  - 事件到来,激活分离器。分离器运行一个非堵塞读操作(它有完毕这个操作所需的所有信息),最后调用相应处理器。

  - 事件处理器处理用户自己定义缓冲区的数据,注冊新的事件(当然相同要给出数据缓冲区地址,须要读取的数据量等信息),最后将控制权返还分离器。

  如我们所见,通过对多路IO模式功能结构的改造,可将Reactor转化为Proactor模式。改造前后,模型实际完毕的工作量没有添加,仅仅只是參与者间对工作职责稍加调换。没有工作量的改变,自然不会造成性能的削弱。对例如以下各步骤的比較,能够证明工作量的恒定:

  标准/典型的Reactor:

  - 步骤1:等待事件到来(Reactor负责)

  - 步骤2:将读就绪事件分发给用户定义的处理器(Reactor负责)

  - 步骤3:读数据(用户处理器负责)

  - 步骤4:处理数据(用户处理器负责)

  改进实现的模拟Proactor:

  - 步骤1:等待事件到来(Proactor负责)

  - 步骤2:得到读就绪事件,运行读数据(如今由Proactor负责)

  - 步骤3:将读完毕事件分发给用户处理器(Proactor负责)

  - 步骤4:处理数据(用户处理器负责)  

  

  对于不提供异步IO API的操作系统来说,这样的办法能够隐藏socket API的交互细节,从而对外暴露一个完整的异步接口。借此,我们就能够进一步构建全然可移植的,平台无关的,有通用对外接口的解决方式。

Scalable IO in Java原文

NIO原理与应用

用NIO开发一个高性能聊天系统

Socket打造高性能server

很多其它NIO专题系列讨论....

server后端性能大比拼

事件驱动编程

并发模型

Rx (Reactive Extensions)介绍

Reactive编程

EDA

2002年大神地址:http://www.jdon.com/concurrent/reactor.htm

Nio学习3——基础模型:多路复用模型的更多相关文章

  1. Java NIO学习系列五:I/O模型

    前面总结了很多IO.NIO相关的基础知识点,还总结了IO和NIO之间的区别及各自适用场景,本文会从另一个视角来学习一下IO,即IO模型.什么是IO模型?对于不同人.在不同场景下给出的答案是不同的,所以 ...

  2. Java网络编程与NIO详解2:JAVA NIO 一步步构建IO多路复用的请求模型

    本文转载自:https://github.com/jasonGeng88/blog 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 http ...

  3. Java网络编程和NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型

    Java网络编程与NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型 知识点 nio 下 I/O 阻塞与非阻塞实现 SocketChannel 介绍 I/O 多路复用的原理 事件选择器与 ...

  4. Java NIO学习与记录(八): Reactor两种多线程模型的实现

    Reactor两种多线程模型的实现 注:本篇文章例子基于上一篇进行:Java NIO学习与记录(七): Reactor单线程模型的实现 紧接着上篇Reactor单线程模型的例子来,假设Handler的 ...

  5. Java NIO学习与记录(六): NIO线程模型

    NIO线程模型 上一篇说的是基于操作系统的IO处理模型,那么这一篇来介绍下服务器端基于IO模型和自身线程的处理方式. 一.传统阻塞IO模型下的线程处理模式 这种处理模型是基于阻塞IO进行的,上一篇讲过 ...

  6. Java NIO学习与记录(五): 操作系统的I/O模型

    操作系统的I/O模型 在开始介绍NIO Reactor模式之前,先来介绍下操作系统的五种I/O模型,了解了这些模型,对理解java nio会有不小的帮助. 先来看下一个服务端处理一次网络请求的流程图: ...

  7. Java NIO学习系列六:Java中的IO模型

    前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型: 阻塞I/O(blocking IO) 非阻塞I/O(nonblocking IO) I/O多路复用(IO multi ...

  8. Java基础(一):I/O多路复用模型及Linux中的应用

    IO多路复用模型广泛的应用于各种高并发的中间件中,那么区别于其他模式他的优势是什么.其核心设计思想又是什么.其在Linux中是如何实现的? I/O模型 I/O模型主要有以下五种: 同步阻塞I/O:I/ ...

  9. 网络编程学习——Linux epoll多路复用模型

    前言 后端开发的应该都知道Nginx服务器,Nginx是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器.后端部署中一般使用的就是Nginx反向代理技术. ...

随机推荐

  1. 去除windows编辑文本中的回车符

    情景描述: 最近,huskiesir的朋友遇到了一个很奇葩的问题.那就是他在windows上搭建了一个http服务,把A脚本放在了上面并用linux去下载和执行,但是在执行的时候出现了问题,在linu ...

  2. golang实现php里的serialize()和unserialize()序列和反序列方法

    Golang 实现 PHP里的 serialize() . unserialize() 安装 go get -u github.com/techleeone/gophp/serialize 用法 pa ...

  3. CSU 1364 Interview RMQ

    题意: 瑶瑶有一家有一家公司,最近他想招m个人.因为他的公司是如此的出名,所以有n个人来参加面试.然而,瑶瑶是如此忙,以至于没有时间来亲自面试他们.所以他准备选择m场面试来测试他们. 瑶瑶决定这样来安 ...

  4. rsyslog学习

    http://blog.csdn.net/zhaoyangjian724/article/details/52116809 http://blog.csdn.net/zhangxihangzhuan/ ...

  5. word中添加Mathtype公式行间距改变问题

    转载链接:http://blog.sciencenet.cn/home.php?mod=space&uid=471807&do=blog&id=616838 最近碰到在word ...

  6. css历史

    CSS目前最新版本为CSS3,是能够真正做到网页表现与内容分离的一种样式设计语言.相对于传统HTML的表现而言,CSS能够对网页中的对象的位置排版进行像素级的精确控制,支持几乎所有的字体字号样式,拥有 ...

  7. Java:JDBC操作

    内容:供程序员调用的接口与类,集成在java.sql和javax.sql包中,如:DriverManager类Connection接口Statement接口ResultSet接口 1.Class.fo ...

  8. 【2017 Multi-University Training Contest - Team 1 1011】KazaQ's Socks

    [Link]:http://acm.hdu.edu.cn/showproblem.php?pid=6043 [Description] 一个人壁橱里有n双袜子,每天早上取一双最小下标的袜子,然后晚上放 ...

  9. [Python] Manipulate Data with Dictionaries in Python

    Dictionaries may be familiar to you as hash maps. In this lesson, you will learn how to create them, ...

  10. 思科2960trunk vlan配置及路由IP配置

    en conf t vlan id end conf t inter rang gi 0/0/1-x switchport access vlan id no shutdown exit (confi ...