|
|
磁盘数据通过网络发送
步骤
- 当应用程序发起
read系统调用时,通过DMA(Direct Memory Access)将数据copy到内核空间buffer - 然后由
CPU控制将内核空间数据copy到用户空间下的buffer中 read调用完成后,write调用首先将用户空间下buffer中的数据copy到内核模式下的socket buffer中- 最后通过
DMA将内核模式下的socket buffer中的数据copy到网卡buffer中
缺点
数据从内核模式到用户模式走了一圈,浪费了两次copy,而这两次copy都是CPU copy(占用CPU资源)
操作系统 零拷贝
|
|
典型场景:内核空间和用户空间的数据拷贝
Linux sendfile
os >= Linux 2.4内核
步骤
DMA copy将磁盘数据copy到kernel buffer中- 向
socket buffer中追加当前要发送的数据在kernel buffer中的位置和偏移量 DMA gather copy(需要网卡支持数据收集模式)根据socket buffer中的位置和偏移量直接将kernel buffer中的数据copy到网卡上
优点
程序只需发出一次系统调用sendfile(),数据只经过了2次copy就从磁盘传送出去了,没有CPU copy
nginx,java FileChannel.transferTo()等在Linux系统中都引用了sendfile机制
FileChannel.transferTo(Java中的零拷贝)
Java NIO中FileChannel.transferTo(long position, long count, WriteableByteChannel target)方法将当前通道中的数据传送到目标通道中,在支持Zero-Copy的linux系统中,transferTo()的实现依赖于sendfile()调用

Netty零拷贝
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。Netty提供了CompositeByteBuf对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。Netty的文件传输采用了java nio transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel(传统的做法: 拷贝文件内容到临时buffer, 然后再将buffer写入Channel),使用操作系统级别的零拷贝,避免了传统通过循环write方式导致的内存拷贝问题
CompositeByteBuf

注意: CompositeByteBuf 是由多个 ByteBuf 组合而成的, 不过在 CompositeByteBuf 内部, 这些 ByteBuf 都是单独存在的, CompositeByteBuf 保存了它们的引用,只是在逻辑上是一个整体
java ByteBuffer vs netty ByteBuf
java 本身就有 ByteBuffer,为什么要额外设计一个 ByteBuf?
ByteBuffer只用一个position变量表示当前位置,所以在进行读写切换的时候都需要调用flip()和clear()等方法,否则功能将出错ByteBuf使用readerIndex和writerIndex分别表示读写位置,不需要调用函数切换,体验更好。