一、什么是零拷贝?(OS层面、用户态和内核态)
9.1 什么是零拷贝? | 小林coding (xiaolincoding.com)
1.传统的文件传输
2.mmap + write 实现零拷贝
mmap()
系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间。
3.sendfile 实现零拷贝
Linux 内核版本必须要 2.1 以上的版本,sendfile() 可以替代前面的 read()
和 write()
这两个系统调用。
4.SG-DMA 实现零拷贝
如果网卡支持SG-DMA技术,网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里。
二、Netty中的零拷贝?(Java层面、用户态)
1.通过堆外内存实现零拷贝
深入netty10-堆外内存_netty堆外内存限制 jvm命令-CSDN博客
直接内存也称为堆外内存,也就是不受JVM控制的内存。
Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
2.通过组合数据实现零拷贝
Netty 允许我们将多段数据合并为一整段虚拟数据供用户使用,而过程中不需要对数据进行拷贝操作。
1.1 TCP 粘包 / 分包 的问题
在 stream-based transport(如 TCP/IP)的传输过程中,数据包有可能会被重新封装在不同的数据包中,例如当你发送如下数据时:
有可能实际收到的数据如下:
因此在实际应用中,很有可能一条完整的消息被分割为多个数据包进行网络传输,而单个的数据包对你而言是没有意义的,只有当这些数据包组成一条完整的消息时你才能做出正确的处理,而 Netty 可以通过零拷贝的方式将这些数据包组合成一条完整的消息供你来使用。而此时,零拷贝的作用范围仅在用户空间中。
1.2 通过 CompositeByteBuf
Netty 提供了 CompositeByteBuf
类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝。
1.3 通过 wrap 操作实现零拷贝
通过 wrap 操作, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而避免了拷贝操作。
1.4 通过 slice 操作实现零拷贝
ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝。
3.通过 FileRegion 实现零拷贝
通过 FileRegion
包装的FileChannel.tranferTo
实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel
, 避免了传统通过循环 write 方式导致的内存拷贝问题。
上述详细讲解都在下面这个链接里,一定要看哦!
java - 对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解 - 后台开发 - SegmentFault 思否
三、传输文件应该注意什么?
- 缓存最近被访问的数据;
- 预读功能;
- PageCache 由于⻓时间被⼤⽂件占据,其他「热点」的⼩⽂件可能就⽆法充分使⽤到 PageCache, 于是这样磁盘读写的性能就会下降了;
- PageCache 中的⼤⽂件数据,由于没有享受到缓存带来的好处,但却耗费 DMA 多拷⻉到 PageCache ⼀次;
所以,传输⽂件的时候,我们要根据⽂件的⼤⼩来使⽤不同的⽅式:
- 传输⼤⽂件的时候,使⽤「异步 I/O + 直接 I/O」;
- 传输⼩⽂件的时候,则使⽤「零拷⻉技术」;