0%

Netty-NIO与零拷贝

Netty-NIO与零拷贝

零拷贝基本介绍

  1. 零拷贝是网络编程的关键,很多性能优化都离不开
  2. 在Java程序中,常用的零拷贝有mmap(内存映射)和sendFile
  3. 零拷贝并非是不拷贝,而是没有CPU拷贝
  4. 所说的零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有kernel buffer有一份数据)
  5. 零拷贝不仅带来更少的数据复制,还能带来其它的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算

传统IO数据读写

  1. Java传统IO和网络编程的一段代码

    1
    2
    3
    4
    5
    6
    File file = new File("test.txt");
    RandomAccessFile raf = new RandomAccessFile(file,"rw");
    byte[] arr = new byte[(int)file.length()];
    raf.read(arr);
    Socket socket = new ServerSocket(8080).accept();
    socket.getOutputStream().write(arr);

mmap优化(三次拷贝,三次交换)

  1. mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数

sendFile优化(三次拷贝,两次交换)

  1. Linux2.1版本提供了sendFile函数,其基本原理:数据根本不经过用户态,直接从内核缓冲区进入到Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换
  2. Linux2.4版本中,做了一些修改,避免了从内核缓冲区拷贝到Socket buffer的操作,从而再一次减少了数据拷贝(两次拷贝,两次交换)(实际上有一次CPU拷贝,从kernel buffer到socket buffer,但拷贝的信息很少,如length、offset,消耗低,可以忽略)

mmap和sendFile的区别

  1. mmap适合小数据量读写,sendFile适合大文件传输
  2. mmap需要4次上下文切换,3次数据拷贝,sendFile需要3次上下文切换,最少2次数据拷贝
  3. sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)

NIO零拷贝案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Server {
public static void main(String[] args) throws IOException {
InetSocketAddress address = new InetSocketAddress(7001);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);

ByteBuffer buffer = ByteBuffer.allocate(4096);
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
int count = 0;
while(-1!=count){
count = socketChannel.read(buffer);
buffer.rewind(); //倒带
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost",7001));
String fileName = "test.zip";
FileChannel fileChannel = new FileInputStream(fileName).getChannel();
long start = System.currentTimeMillis();
// 在Linux下一个 transferTo方法就可以完成传输
// 在Windows下一次调用transferTo最多只能发送8m,就需要分段传输,且要注意传输时的位置
// transferTo底层使用零拷贝
long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
System.out.println("发送总字节数:"+transferCount);
System.out.println("耗时:"+(System.currentTimeMillis()-start));
}
}

AIO基本介绍

  1. JDK 7引入了Asynchronous I/O,即AIO。在进行I/O编程中,常用到两种模式Reactor和Proactor,Java的NIO就是Reactor,当有事件触发时,服务器得到通知,进行相应的处理
  2. AIO即NIO2.0,叫做异步不阻塞的IO,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
  3. 目前AIO还没有广泛应用,Netty也是基于NIO,而不是AIO

BIO、NIO、AIO对比表

BIO NIO AIO
IO模型 同步阻塞 同步非阻塞(多路复用) 异步非阻塞
编程难度 简单 复杂 复杂
可靠性
吞吐量

举例说明:

  1. 同步阻塞:到理发店理发,就一直等理发师,直到轮到自己理发
  2. 同步非阻塞:到理发店理发,发现前面有其它人理发,给理发师说下,先做其它事情,一会过来看是否轮到自己
  3. 异步非阻塞:给理发师打电话,让理发师上门服务,自己做其它事件,理发师自己来家给你理发(或到理发店理发,发现前面有其它人理发,告诉理发师,先做其它事,轮到自己时,理发师来通知)