|
|
磁盘数据通过网络发送
步骤
- 当应用程序发起
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
分别表示读写位置,不需要调用函数切换,体验更好。