性能测试

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. 业务逻辑简单,处理时间短,例如网络聊天室,网络游戏等


参考:


版权声明:本文为博主原创文章,未经博主允许不得转载。

NIO vs. BIO的更多相关文章

  1. NIO 和BIO

    讲讲NIO? 传统的IO流是阻塞式的,会一直监听一个ServerSocket,在调用read等方法时,他会一直等到数据到来或者缓冲区已满时才返回.调用accept也是一直阻塞到有客户端连接才会返回.每 ...

  2. AIO、NIO、BIO

    AIO:异步非阻塞 NIO:同步非阻塞 BIO:同步阻塞 (1)同步 指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 (2)异步 指用户进程触发IO操作以后便开始做自己的事情,而当I ...

  3. 基于NIO和BIO的两种服务器对比

    基于BIO的服务器,服务端可能要同时保持几百万个HTTP连接,而这些连接并不是每时每刻都在传输数据,所以这种情况不适合使用BIO的服务器:而且需要保证共享资源的同步与安全,这个实现起来相对复杂.这时候 ...

  4. nio、bio区别,应运场景

    bio阻塞i/o a.面向流的,InputStream(),OuputStream字节输入流,字节输出流,Reader,Writer字符输入流,字符输出流 b.阻塞的IO,比如Socket,它的底层用 ...

  5. NIO、BIO、AIO区别

    一.同步阻塞I/O(BIO): 同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机 ...

  6. NIO,AIO,BIO

    同步和异步:同步和异步关注的是消息通信机制, 同步:就是在发出一个“调用”时,在没有得到结果之前,该“调用”就不返回,但是一旦调用返回,就得到返回值了;换句话说:就是由“调用者”主动等待“调用”结果 ...

  7. java NIO、BIO、AIO全面剖析

    在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解.具体如下: 序号 问题 1 什么是同步? 2 什么是异步? 3 什么是阻塞? 4 什么是非阻塞? 5 什么是同步阻塞? 6 什么是同步 ...

  8. Java中NIO、BIO、AIO相关概念及应用场景

    1.同步阻塞IO(JAVA BIO):同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时,服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通 ...

  9. Java IO、BIO、NIO、BIO

    一.什么是IO/NIO: IO:即BIO(Blocking IO):面向流的.同步阻塞式IO:(JDK1.4之前唯一的选择) NIO:面向缓冲的.同步非阻塞式IO:三大核心部分:Selector.Ch ...

随机推荐

  1. jquery分页插件

    css代码: /*分页*/ .pageList { clear: both; overflow: hidden; } .pageList a, .pageList span { border: 1px ...

  2. 超链接实现post方式提交

    思路:如果想要超链接实现post方式提交,必须借助表单.下面得两种方式,一种是借助显示的form表单,一种是借助隐式的form表单方式一:将超链接放到一个form表单中,或者超链接本身就在一个form ...

  3. Python基础篇【第5篇】: Python模块基础(一)

    模块 简介 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护. 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就 ...

  4. (dp)343. Integer Break

    Given a positive integer n, break it into the sum of at least two positive integers and maximize the ...

  5. php和js如何通过json互相传递数据

    当我们在结合php和javascript实现某些功能时,经常会用到json.json是js的一种数据格式,可以直接被js解析.而php无法直接读取json数据,但是php提供了json_decode函 ...

  6. 网络编程(一):用C#下载网络文件的2种方法

    使用C#下载一个Internet上的文件主要是依靠HttpWebRequest/HttpWebResonse和WebClient.具体处理起来还有同步和异步两种方式,所以我们其实有四种组合. 1.使用 ...

  7. jpeg库编译 - windows平台

    一.准备: 下载最新的jpeg库源码:http://www.ijg.org/files/jpegsr9a.zip二.编译 1. 解压到指定目录 2. 打开VS2010命令行窗口(为了得到VS2010的 ...

  8. IIS发布文件出现:未能加载文件或程序集“xxxx”或它的某一个依赖项。试图加载格式不正确的程序。

    解决方案:IIS——应用程序池—选中网站—高级设置——启用32位应用程序 :true.

  9. oracle 实现ID自增

    CREATE TABLE testTable1 ( ID INT NOT NULL, NAME ) NOT NULL, PRIMARY KEY(ID) ) TABLESPACE MYDB; --创建自 ...

  10. Thinkphp源码分析系列(五)–系统钩子实现

    Thinkphp的插件机制主要依靠的是Hook.class.php这个类,官方文档中在行为扩展也主要依靠这个类来实现.下面我们来具体看看tp是怎么利用这个类来实现行为扩展的. 首先,行为扩展是什么?有 ...