NIO vs. BIO

性能测试

BIO -- Blocking IO 即阻塞式IO

NIO -- Non-Blocking IO, 即非阻塞式IO或异步IO

性能 -- 所谓的性能是指服务器响应客户端的能力,对于服务器我们通常用并发客户连接数+系统响应时间来衡量服务器性能,例如,我们说这个服务器在10000个并发下响应时间是100ms,就是高性能,而另一个服务器在10个并发下响应时间是500ms,性能一般。所以提升性能就是提升服务器的并发处理能力,和缩短系统的响应时间。

测试方法

用同一个Java Socket Client 分别调用用BIO和NIO实现的Socket Server, 观察其建立一个Socket (TCP Connection)所需要的时间,从而计算出Server吞吐量TPS。

之所以可以用Connection建立时间来计算TPS,而不考虑业务逻辑运行时间,是因为这里的业务逻辑很简单,只是Echo回从client传过来的字符,所消耗时间可以忽略不计。

注意: 在现实场景中,业务逻辑会比较复杂,TPS的计算必须综合考虑IO时间+业务逻辑执行时间+多线程并行运行情况 等因素的影响。

测试类

1. Java Socket Client

public class PlainEchoClient {

	public static void main(String args[]) throws Exception {
		for (int i = 0; i < 20; i++) {
			startClientThread();
		}
	}

	private static void startClientThread() throws UnknownHostException,
			IOException {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					startClient();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		t.start();
	}

	private static void startClient() throws UnknownHostException, IOException {
		long beforeTime = System.nanoTime();
		String host = "127.0.0.1";
		int port = 8086;
		Socket client = new Socket(host, port);
		// 建立连接后就可以往服务端写数据了
		Writer writer = new OutputStreamWriter(client.getOutputStream());
		writer.write("Hello Server.");
		writer.flush();
		// 写完以后进行读操作
		Reader reader = new InputStreamReader(client.getInputStream());
		char chars[] = new char[64];// 假设所接收字符不超过64位,just for demo
		int len = reader.read(chars);
		StringBuffer sb = new StringBuffer();
		sb.append(new String(chars, 0, len));
		System.out.println("From server: " + sb);
		writer.close();
		reader.close();
		client.close();
		System.out.println("Client use time: "
				+ (System.nanoTime() - beforeTime) + " ns");
	}
}

2. IO Socket Server

这个Socket Server模拟的是我们经常使用的thread-per-connection模式, Tomcat,JBoss等Web Container都是这种方式。

public class PlainEchoServer {
	private static final ExecutorService executorPool = Executors.newFixedThreadPool(5);

	private static class Handler implements Runnable{
		private Socket clientSocket;
		public Handler(Socket clientSocket){
			this.clientSocket = clientSocket;
		}

		@Override
		public void run() {
			try {
				BufferedReader reader = new BufferedReader(
						new InputStreamReader(
								clientSocket.getInputStream()));
				PrintWriter writer = new PrintWriter(
						clientSocket.getOutputStream(), true);
				char chars[] = new char[64];
				int len = reader.read(chars);
				StringBuffer sb = new StringBuffer();
				sb.append(new String(chars, 0, len));
				System.out.println("From client: " + sb);
				writer.write(sb.toString());
				writer.flush();
			} catch (IOException e) {
				e.printStackTrace();
				try {
					clientSocket.close();
				} catch (IOException ex) {
					// ignore on close
				}
			}
		}
	}		

	public void serve(int port) throws IOException {
		final ServerSocket socket = new ServerSocket(port);
		try {
			while (true) {
				long beforeTime = System.nanoTime();
				final Socket clientSocket = socket.accept();
				System.out.println("Establish connection time: "+ (System.nanoTime()-beforeTime)+" ns");
				executorPool.execute(new Handler(clientSocket));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws IOException{
		PlainEchoServer server = new PlainEchoServer();
		server.serve(8086);
	}
}

3. NIO Socket Server

public class PlainNioEchoServer {
	public void serve(int port) throws IOException {
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		ServerSocket ss = serverChannel.socket();
		InetSocketAddress address = new InetSocketAddress(port);
		ss.bind(address); // #1
		serverChannel.configureBlocking(false);
		Selector selector = Selector.open();
		serverChannel.register(selector, SelectionKey.OP_ACCEPT); // #2
		while (true) {
			try {
				selector.select(); // #3
			} catch (IOException ex) {
				ex.printStackTrace();
				// handle in a proper way
				break;
			}
			Set readyKeys = selector.selectedKeys(); // #4
			Iterator iterator = readyKeys.iterator();
			while (iterator.hasNext()) {
				SelectionKey key = (SelectionKey) iterator.next();
				try {
					if (key.isAcceptable()) {
						ServerSocketChannel server = (ServerSocketChannel) key
								.channel();
						long beforeTime = System.nanoTime();
						SocketChannel client = server.accept(); // #6
						System.out.println("Accept connection time: "+ (System.nanoTime()-beforeTime)+" ns");
						if (client == null){//Check if socketChannel has been created, it could be null, because it's non-blocking
							continue;
						}
						client.configureBlocking(false);
						client.register(selector, SelectionKey.OP_WRITE
								| SelectionKey.OP_READ,
								ByteBuffer.allocate(100));
					}
					if (key.isReadable()) {
						SocketChannel client = (SocketChannel) key.channel();
						ByteBuffer output = (ByteBuffer) key.attachment();
						client.read(output);
					}
					if (key.isWritable()) {
						SocketChannel client = (SocketChannel) key.channel();
						ByteBuffer output = (ByteBuffer) key.attachment();
						output.flip();
						client.write(output);
						output.compact();
					}
				} catch (IOException ex) {
					key.cancel();
					try {
						key.channel().close();
					} catch (IOException cex) {
					}
				}
				iterator.remove(); // #5
			}
		}
	}

	public static void main(String[] args) throws IOException{
		PlainNioEchoServer server = new PlainNioEchoServer();
		server.serve(8086);
	}
}

测试结果

Socket Client <——> IO Socket Server

Establish connection time: 2183277 ns
Establish connection time: 1523264 ns
Establish connection time: 1430883 ns

平均耗费时间大概是1.5 ms,TPS 大概是600 

Socket Client <——> NIO Socket Server

Accept connection time: 138059 ns
Accept connection time: 132927 ns
Accept connection time: 132413 ns

平均耗费时间大概是0.15 ms,TPS 大概是6000

从测试结果可以看出,NIO的接受请求的速率大概是IO的十倍。

NIO还是BIO

在探讨在什么场景下使用BIO,什么场景下使用NIO之前,让我们先看一下在两种不同IO模型下,实现的服务器有什么不同。

BIO Server

通常采用的是request-per-thread模式,用一个Acceptor线程负责接收TCP连接请求,并建立链路(这是一个典型的网络IO,是非常耗时的操作),然后将请求dispatch给负责业务逻辑处理的线程池,业务逻辑线程从inputStream中读取数据,进行业务处理,最后将处理结果写入outputStream,自此,一个Transaction完成。

Acceptor线程是服务的入口,任何发生在其上面的堵塞操作,都将严重影响Server性能,假设建立一个TCP连接需要4ms,无论你后面的业务处理有多快,因为Acceptor的堵塞,这个Server最多每秒钟只能接受250个请求。而NIO则是另外一番风景,因为所有的IO操作都是非堵塞的,毫无疑问,Acceptor可以接受更大的并发量,并能最大限度的利用CPU和硬件资源处理这些请求。

BIO通信模型图

BIO序列图

NIO Server

如下图所示,在NIO Server中,所有的IO操作都是异步非堵塞的,Acceptor的工作变的非常轻量,即将IO操作分派给IO线程池,在收到IO操作完成的消息通知时,指派业务逻辑线程池去完成业务逻辑处理,因为所有的耗时工作都是异步的,使得Acceptor可以以非常快的速度接收请求,10W每秒是完全有可能的。

10W/S可能是没有考虑业务处理时间,考虑到业务时间,现实场景中,普通服务器可能很难做到10W TPS,为什么这么说呢?试想下,假设一个业务处理需要500ms,而业务线程池中只有50个线程,假设其它耗时忽略不计,50个线程满负载运行,在50个并发下,大家都很happy,所有的Client都能在500ms后获得响应. 在100个并发下,因为只有50个线程,当50个请求被处理时,另50个请求只能处在等待状态直到有可用线程为止。也就是说,理想情况下50个请求会在500ms返回,另50个可能会在1000ms返回。以此类推,若是10000个并发,最慢的50个请求需要100S才能返回。

以上做法是为线程池预设50个线程,这是相对保守的一种做法,其好处是不管有多少个并发请求,系统只有这么多资源(50个线程)提供服务,是一种时间换空间的做法,也许有的客户会等很长时间,甚至超时,但是服务器的运行是平稳的。 还有一种比较激进的线程池模型是类似Netty里推荐的弹性线程池,就是没有给线程池制定一个线程上线,而是根据需要,弹性的增减线程数量,这种做法的好处是,并发量加大时,系统会创建更多的线程以缩短响应时间,缺点是到达一个极限时,系统可能会因为资源耗尽(CPU 100%或者Out of Memory)而down机。

所以可以这样说,NIO极大的提升了服务器接受并发请求的能力,而服务器性能还是要取决于业务处理时间和业务线程池模型。

NIO序列图

什么时候使用BIO?

1. 低负载、低并发的应用程序可以选择同步阻塞IO以降低编程复杂度。

2. 业务逻辑耗时过长,使得NIO节省的时间显得微不足道。

什么时候使用NIO?

1. 对于高负载、高并发的网络应用,需要使用NIO的非阻塞模式进行开发。

2. 业务逻辑简单,处理时间短,例如网络聊天室,网络游戏等

参考:

http://www.infoq.com/cn/articles/netty-high-performance

http://tutorials.jenkov.com/java-nio/nio-vs-io.html

时间: 2024-11-02 11:51:41

NIO vs. BIO的相关文章

NIO vs. BIO 我该如何选择

本文介绍了NIO和BIO的工作原理,并通过一组性能测试,对NIO和BIO的性能进行对比,为如何选择NIO和BIO提供理论和实践依据. 术语介绍 BIO -- Blocking IO 即阻塞式IO.NIO -- Non-Blocking IO, 即非阻塞式IO或异步IO.性能 -- 所谓的性能是指服务器响应客户端的能力,对于服务器我们通常用并发客户连接数+系统响应时间来衡量服务器性能,例如,我们说这个服务器在10000个并发下响应时间是100ms,就是高性能,而另一个服务器在10个并发下响应时间是

Tomcat的BIO和NIO问题

前言 最近一些朋友通过书籍找到我,问了一些关于tomcat中BIO和NIO的问题,这里列一下方便需要的朋友.后续也将前面有朋友问的问题整理下.. 问 只把 Tomcat 的 bio 模式改为 nio 模式,是否能提高服务器的吞吐量?发现在配置一样的情况下,两种模式压出来的吞吐量差不多. 答 要看你系统是不是整个都异步化了,因为tomcat的nio只是将网络io异步化了,就是接收和读写异步化了,但是网络报文接受完后还是要交给业务线程池,如果你的业务是阻塞或者说较耗时的话是没办法提升你整个的吞吐量的

Java NIO概览与应用

关于NIO Java NIO即Java Non-blocking IO(Java非阻塞I/O),是Jdk1.4之后增加的一套操作I/O工具包,又被叫做Java New IO. (1)Reactor模式 Reactor即反应器,就是我们将事件注册到Reactor中,当有相应的事件发生时,Reactor便会告知我们有哪些事件发生了,我们再根据具体的事件去做相应的处理.在NIO里主要是Selector多路复用模型. (2)BIO(同步阻塞IO)和NIO的区别 BIO在调用read/write的时候会阻

使用jetty做为server提供多线程文件下载

背景   最近在做的一个项目,两个java进程之间会涉及一个大数据量的传递过程,基本都是图片文件,(做了压缩后还是会比较大,最大的有超过600MB).其次这两个java进程是在跨机房,比如中国和美国机房,网络待框也就几百kB.     这就是本文的项目背景 分析 1.  600MB的文件,都是A进程运行时根据需要生成的(下载需要的图片文件).所以无法预先处理,而且公司总图片文件都是以TB计算,所以全量同步的方案也不靠谱 2.  从A进程到B进程的数据传递,首先想到用socket进行传递,但单so

http get post 慢速攻击

提起攻击,第一反应就是海量的流量.海量的报文.但有一种攻击却反其道而行之,以慢著称,以至于有些攻击目标被打死了都不知道是怎么死的,这就是慢速连接攻击. slowhttptest是一款对服务器进行慢攻击的测试软件,包含了几种攻击方式,像Slowloris.SlowHTTP POST.Slow Read attack等. 总而言之,该工具的原理就是设法让服务器等待,当服务器在保持连接等待时,就消耗了资源. 1. 最具代表性的是rsnake发明的Slowloris,又被称为slow headers.

RPC or noRPC,这是个问题

/** * author:ahuaxuan * date:2010-04-21 */ 修改,避免引起混淆,特别说明本文中的非RPC方式其本质也是RPC,只是非RPC由服务器端定义好序列化规则和协议,然后让调用者自己去实现,而本文中的RPC指服务提供者提供Jar,客户端可以直接调用接口.不需要考虑到网络,协议,序列化算法. 很多公司都会遇到应用集成的一些问题,其中一项就是RPC的问题. 企业内部应用集成(请求应答模式)的通信一般有方式,一种是RPC方式,另外一个是非RPC方式. 先说说非RPC方式

Tomcat7.0.26的连接数控制bug的问题排查

感谢同事[空蒙]的投稿. 首先感谢@烈元一起排查此问题.今天发现线上一台机器,监控一直在告警,一看是健康检查不通过,就上去查看了下,首先自己curl了下应用的url,果然是超时没有响应,那就开始按顺序排查了: 1. load非常低,2.gc也正常,3.线程上也没死锁,4.日志一切正常.那是什么情况呢,不能忘记网络啊.果然,netstat命令一把,结果如下: TIME_WAIT 68 CLOSE_WAIT 194 ESTABLISHED 3941 SYN_RECV 100 问题出来了,SYN_RE

Netty代码分析

Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序[官方定义],整体来看其包含了以下内容:1.提供了丰富的协议编解码支持,2.实现自有的buffer系统,减少复制所带来的消耗,3.整套channel的实现,4.基于事件的过程流转以及完整的网络事件响应与扩展,5.丰富的example.本文并不对Netty实际使用中可能出现的问题做分析,只是从代码角度分析它的架构以及实现上的一些关键细节. 首先来看下最如何使用Netty(其自带example

学习Java中需要注意那些重点知识?

问题描述 我是长沙清华it的一名学员,今年才开始接触Java方面的知识,我想问一下,学习Java需要怎么样去学,才学的更好.谢谢各位大师人物· 解决方案 解决方案二:语法完了...面向对象掌握好...集合IO...一直下去多看多敲...不要眼高手低就好解决方案三:重点结合hibernate理解面向对象的思想,结合struts理解MVC既web开发,结合spring理解ioc解决方案四:其实学习这门语言还是收获到很多的启示的学习永远没有旁观者别人会的,我们也会.谢谢大家的建议和意见.解决方案五:推