赞
踩
一、概述
传统IO的文件传输,数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。
期间发生了两次系统调用,一次是 read() ,一次是 write(),每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。
传输过程:
传统IO总结:
传统IO的文件传输期间一共发生了 4 次用户态与内核态的上下文切换和4 次数据拷贝(其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝的)
二、零拷贝原理
所谓的零拷贝就是减少用户态与内核态的上下文切换和内存拷贝的次数。
零拷贝技术实现的方式通常有两种:
2.1、mmap + write
mmap() 系统调用函数会直接把内核缓冲区里的数据映射到用户空间,这样操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。
总结:mmap 减少一次数据拷贝的过程(3次内存拷贝)+ 4 次上下文切换(因为系统调用还是 2 次)
2.2、sendfile
在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile():
从 Linux 内核 2.4 版本开始起,网卡支持 SG-DMA 技术,sendfile() 系统调用的过程发生了变化:
总结:sendfile 只需要 2 次上下文切换和2 次数据拷贝次数,就可以完成文件的传输。
2.3、mmap 与 sendfile 区别
总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上。
三、 Java 中的应用
3.1、MappedByteBuffer
Java NlO 中 的 MappedByteBuffer 就是通过 FileChannel.map() 方法获得,其实就是采用了操作系统中的内存映射方式,底层就是调用 Linux mmap() 实现的。
MappedByteBuffer mappedByteBuffer = new RandomAccessFile(file, "r")
.getChannel()
.map(FileChannel.MapMode.READ_ONLY, 0, len);
使用 MappedByteBuffer 类要注意的是:mmap的文件映射,在 full gc 时才会进行释放。当 close 时,需要手动清除内存映射文件,可以反射调用 sun.misc.Cleaner 方法。
3.2、sendfile
FileChannel.transferTo() 方法直接将当前通道内容传输到另一个通道,没有涉及到 Buffer 的任何操作,transferTo() 的实现方式就是通过系统调用 sendfile() 实现的。
// 使用sendfile:读取磁盘文件,并网络发送
FileChannel sourceChannel = new RandomAccessFile(source, "rw").getChannel();
SocketChannel socketChannel = SocketChannel.open(sa);
sourceChannel.transferTo(0, sourceChannel.size(), socketChannel);
以前写过的Kafka 和RocketMQ,消费消息时就是零拷贝,只不过RocketMQ 在消费消息时,使用了 mmap。Kafka 使用了 sendFile。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。