零拷贝的概念
零拷贝是站在操作系统角度来观察的,并不是完全不拷贝数据,只是拷贝的工作完全由操作系统来完成。具体来说,从操作系统角度看,在读取和写入数据的过程中,CPU 没有参与拷贝数据,所以叫零拷贝。
DMA 是什么?
DMA(Direct Memory Access)是 IO 设备与主存之间的一个直接数据通路,不需要 CPU 直接拷贝数据到内存,可以直接用 DMA 拷贝数据到内存。
传统的 Java 网络 IO 编程是怎样的?
File file = new File("index.jsp");
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);
代码中对应的 read 和 write 方法在操作系统底层是什么样子的呢?
在数据读取和写入的过程中,会发生2次 DMA 拷贝,2次 CPU 拷贝,4次上下文切换。具体可以参考下图。
mmap
mmap 通过内存映射,将文件直接映射到内存,减少了一次内核空间到用户空间的拷贝。
在数据读取和写入的过程中,会发生2次 DMA 拷贝,1次 CPU 拷贝,4次上下文切换。具体可以参考下图。
sendFile
Linux 2.1 开始提供了 sendFile 函数,其原理是,数据根本不经过用户态,直接从 kernel buffer 拷贝到 socket buffer,减少了一次上下文切换。
在数据读取和写入的过程中,会发生2次 DMA 拷贝,1次 CPU 拷贝,3次上下文切换。具体可以参考下图。
Linux 2.4 版本中做了进一步的优化,从 kenerl buffer 到 socket buffer 的拷贝也省了,直接从 kernel buffer 拷贝到协议栈。
在数据读取和写入的过程中,会发生2次 DMA 拷贝,0次 CPU 拷贝,3次上下文切换,这就是所谓的零拷贝。具体可以参考下图。
实际开发中,mmap 和 sendFile 都有使用,可以认为是零拷贝的2种实现方式。mmap 适合少量数据读写,sendFile 适合大文件的传输。sendFile 可以利用 DMA 将数据直接从内核缓冲区拷贝到协议栈,减少一次 CPU拷贝,mmap 则无法避免。
案例
RocketMQ 在 CommitLog 和 ConsumeQueue 都使用了 mmap 。Kafka 则使用了 sendFile。
RocketMQ 和 Kafka 高性能的原因之一就是顺序写入和近似顺序读取+零拷贝。