Java I/O API之性能分析 (下)

原创 2004年12月30日 10:11:00
 
四、注册与处理过程详解

  接下来我们要分析Connection的register()方法。前面我们总是说用Selector注册的连接,其实这是一种简化的说法。实际上,用Selector注册的是一个java.nio.channels.SocketChannel对象,但只针对特定的I/O操作。注册之后,有一个 java.nio.channels.SelectionKey被返回。这个选择键可以通过attach()方法关联到任意对象。为了通过键获得连接,这里把Connection对象关联到键。这样,我们就可以从Selector间接地获得一个Connection。


  public void register(Selector selector)
  throws IOException {
  key = socketChannel.register(selector, SelectionKey.OP_READ);
  key.attach(this);
  }


  回过头来看ConnectionSelector。select()方法的返回值表示有多少连接已经做好了I/O操作的准备。如果返回值是0,则返回;否则,调用selectedKeys()获得键的集合(Set),从这些键获得以前关联的Connection对象,然后调用其 readRequest()或writeResponse()方法,具体调用哪一个方法由连接被注册为读取操作还是写入操作决定。

  现在再来看Connection类。Connection类代表着连接,处理所有协议有关的细节。在构造函数中,通过参数传入的 SocketChannel被设置成非阻塞模式,这对于服务器来说是很重要的。另外,构造函数还设置了一些默认值,分配了缓冲区 requestLineBuffer。由于分配直接缓冲区代价稍高,且这里的每一个连接都用一个新的缓冲区,因此这里使用 java.nio.ByteBuffer.allocate()而不是ByteBuffer.allocateDirect()。如果重用缓冲区,直接缓冲区可能具有更高的效率。


  public Connection(SocketChannel socketChannel)
  throws IOException {
  this.socketChannel = socketChannel;
  ...
  socketChannel.configureBlocking(false);
  requestLineBuffer = ByteBuffer.allocate(512);
  ...
  }


  完成所有初始化工作且SocketChannel做好了读取准备之后,ConnectionSelector调用了readRequest()方法,利用socketChannel.read(requestLineBuffer)方法把所有可用的数据读入缓冲区。如果不能读取完整的行,则返回发出调用的ConnectionSelector,允许另一个连接进入处理过程;反之,如果成功地读取了整个行,接下来应该做的是象在Httpd中一样解析请求。如果当前的请求合法,程序为请求目标文件创建一个java.nio.Channels.FileChannel,并调用 prepareForResponse()方法。


  private void prepareForResponse() throws IOException {
  StringBuffer responseLine = new StringBuffer(128);
  ...
  responseLineBuffer = ByteBuffer.wrap(
  responseLine.toString().getBytes("ASCII")
  );
  key.interestOps(SelectionKey.OP_WRITE);
  key.selector().wakeup();
  }

prepareForResponse()方法构造出缓冲区responseLine以及(如果必要的话)应答头或错误信息,并把这些数据写入 responseLineBuffer。这个ByteBuffer是一个byte数组的简单的封装器。生成待输出的数据之后,我们还要通知 ConnectionSelector:从现在开始不再读取数据,而是要写入数据了。这个通知通过调用选择键的interestedOps (SelectionKey.OP_WRITE)方法完成。为了保证选择器能够迅速认识到连接操作状态的变化,接着还要调用wakeup()方法。接下来 ConnectionSelector调用连接的writeResponse()方法。首先,responseLineBuffer被写入到Socket 管道。如果缓冲区的内容全部被写入,而且还有被请求的文件需要发送,接着调用前面打开的FileChannel的transferTo()方法。 transferTo()方法通常能够高效地把数据从文件传输到管道,但实际的传输效率依赖于底层的操作系统。任何时候,被传输的数据量至多相当于在无阻塞的情况下可写入目标管道的数据量。为安全和确保各个连接之间的公平起见,这里把上限设置成64 KB。

  如果所有数据都已经传输完毕,close()执行清理工作。取消Connection的注册是这里的主要任务,具体通过调用键的cancel()方法完成。


  public void close() {
  ...
  if (key != null) key.cancel();
  ...
  }


  这个新的方案性能如何呢?答案是肯定的。从原理上看,一个Acceptor和一个ConnectionSelector足以支持任意数量的打开的连接。因此,新的实现方案在可伸缩性方面占有优势。但是,由于两个线程必须通过同步的queue()方法通信,它们可能互相阻塞对方。解决这个问题有两种途径:

  ·改进实现队列的方法
  ·采用多个Acceptor/ConnectionSelector对

  与Httpd相比,NIOHttpd的一个缺点是,对于每一个请求,就有一个新的带缓冲的Connection对象被创建。这就导致了垃圾收集器产生的额外的CPU占用,这部分附加代价的具体程度又与VM的类型有关。然而,Sun不厌其烦地强调说,有了Hotspot,短期生存的对象不再成为问题。

  五、可伸缩性的定量分析和比较

  在可伸缩性方面,NIOHttpd到底比Httpd好多少?下面我们来看看具体的数字。首先要声明的是,这里的数字具有大量的推测成分,一些重要的环境因素,例如线程同步、上下文切换、换页、硬盘速度和缓冲等,都没有考虑到。首先评估处理r个并发的请求需要多少时间,假设被请求的文件大小是s字节,客户端的带宽是b字节/秒。对于Httpd,这个时间显然直接依赖于线程的数量t,因为同一时刻只能处理t个请求。所以Httpd的处理时间可以从公式一得到,其中c是执行请求分析之类操作的开销常量,这个值对于每一个请求来说都是一样的。另外,这里假定从磁盘读取数据的速度总是快于写入Socket的速度,服务器带宽总是大于客户机带宽之和,且CPU未满载。因此,服务器端的带宽、缓冲和硬盘速度等因素都不必在该公式中考虑。

20_1.gif
然而,NIOHttpd的处理时间不再依赖于t。对于NIOHttpd,传输时间l在很大程度上依赖于客户端的带宽b、文件大小s以及前面提到的常数c。由此可以得出公式二,从该公式可以得到NIOHttpd的最小传输时间。
20_2.gif
注意公式三的比值d,它度量了NIOHttpd和Httpd的性能对比关系。
20_3.gif
进一步的分析表明,如果s、b、t和c是常数,r 趋向无穷时d的增长趋向于一个极限,从公式四可以方便地计算出这个极限。
20_4.gif
因此,除了线程的数量和常量性的开销,连接的时长s/b对d具有极端重要的影响。连接持续的时间越长,d值越小,NIOHttpd对比Httpd的优势也就越高。表一显示出,当c=10ms,t=100,s=1mb,b=8kb/s时,NIOHttpd要比Httpd快126倍。如果连接持续了很长一段时间,NIOHttpd表现出巨大的优势。当连接时间较短时,例如在100 Mb的局域网内,如果文件较大,NIOHttpd表现出10%的优势;如果文件较小,优势不明显。

【精】Linux磁盘I/O性能监控之iostat详解

iostat 监视I/O子系统iostat是I/O statistics(输入/输出统计)的缩写,用来动态监视系统的磁盘操作活动。11.1. 命令格式iostat[参数][时间][次数]11.2. 命...
  • sunansheng
  • sunansheng
  • 2016年07月18日 15:28
  • 8648

【测试】Jmeter测试CPU、I/O等服务器性能

ServerAgent JMeterPlugins Linux
  • sky15732625340
  • sky15732625340
  • 2017年02月25日 20:55
  • 3167

JMeter监控服务器CPU、内存及I/O ——plugin插件监控被测系统资源方法

jmeter中也可以监控服务器的CPU和内存使用情况,但是需要安装一些插件还需要在被监测服务器上开启服务。   1.需要的插件准备 JMeterPlugins-Standard-1.3.1...
  • sinat_35743306
  • sinat_35743306
  • 2016年09月17日 15:11
  • 2244

使用异步 I/O 大大提高应用程序的性能:学习何时以及如何使用 POSIX AIO API

简介: Linux® 中最常用的输入/输出(I/O)模型是同步 I/O。在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止。这是很好的一种解决方案,因为调用应用程序在等待 I/O 请求完...
  • ajian005
  • ajian005
  • 2012年12月26日 16:59
  • 493

Java 文件I/O常用API 以及上传下载路径问题汇总

最近做项目,主要是将本地图片、音频上传到数据库中,在下载在指定文件路径,程序读取该路径进行显示和播放音频。通过这个功能,对JAVA 文件流以及上传下载的相关路径进行总结。 一、File类的使用 首先,...
  • Daybreak1209
  • Daybreak1209
  • 2016年01月06日 21:20
  • 1222

Java I/O通用api设计 (一)

原文A generic input/output API in Java(by Rickard Öberg)中给出了一个通用Java IO API设计,并且有API的Demo代码。更重要的一点是,...
  • ykdsg
  • ykdsg
  • 2013年04月15日 17:31
  • 605

服务端I/O性能大比拼:Node、PHP、Java、Go

原文:Server-side I/O Performance: Node vs. PHP vs. Java vs. Go 作者:BRAD PEABODY 翻译:雁惊寒 摘要:本文首先简单...
  • dev_csdn
  • dev_csdn
  • 2017年12月21日 15:15
  • 18107

服务端I/O性能大比拼:Node、PHP、Java、Go

摘要:本文首先简单介绍了I/O相关的基础概念,然后横向比较了Node、PHP、Java、Go的I/O性能,并给出了选型建议。以下是译文。 了解应用程序的输入/输出(I/O)模型能够更好的理解它在...
  • ruanchengmin
  • ruanchengmin
  • 2017年12月21日 20:43
  • 92

比较java新旧I/O的性能——以复制大文件为例

比较Java新旧I/O的性能——以复制大文件为例
  • zpcandzhj
  • zpcandzhj
  • 2015年01月14日 22:30
  • 736

服务端I/O性能大比拼:Node、PHP、Java、Go

摘要:本文首先简单介绍了I/O相关的基础概念,然后横向比较了Node、PHP、Java、Go的I/O性能,并给出了选型建议。以下是译文。 了解应用程序的输入/输出(I/O)模型能够更好的理解它在...
  • qq_40714902
  • qq_40714902
  • 2017年12月21日 23:19
  • 92
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章: Java I/O API之性能分析 (下)
举报原因:
原因补充:

(最多只允许输入30个字)