Netty的零拷贝是怎么实现的?
2 个回答
你该反省的是你的见识与眼光,而不是你的真诚和善良。张子陵# Netty 零拷贝机制 ### 用户空间与内核空间 操作系统的核心是内核,它不同于普通应用程序,所以为了保护内核的存储空间,操作系统将虚拟空间分为了两个部分:内核空间和用户空间  ### 传统IO流程 - DMA:直接内存访问,是主板上的一块独立芯片,允许外设设备和内存之间进行数据IO传输 - 四次上下文切换,四次拷贝  ## 什么是零拷贝 首先从字面意思可以将“零拷贝”拆成“零”和“拷贝”两个概念 - 零:指的是拷贝次数相对较少,并不是真的拷贝次数为零 - 拷贝:指数据从一个存储空间拷贝到另一个存储空间 连起来的意思就是:通过零拷贝技术,减少将数据从一个存储空间复制到另一个空间的次数 ## 为什么要使用零拷贝 - 在传统IO数据传输中,拷贝次数和上下文切换次数太多。 - 通过使用零拷贝,能实现减少上下文切换的次数和数据拷贝的次数。 ## 零拷贝常见的实现方式 ### mmap + write 我们在前面内容介绍过,用户进程在调用read函数读取数据时,操作系统会将内核缓冲区的数据拷贝一份到用户空间缓冲区,所以为了减少这一步开销,我们可以使用mmap代替read函数。 mmap将内核空间缓冲区的数据直接映射到用户空间,这样就不需要再拷贝一份数据了,但是还是会有上下文切换的。  优化点:三次拷贝+四次上下文切换 ### sendfile sendfile实现有两个版本,我们这里只介绍真正实现零拷贝技术的后者,即通过SG-DMA技术实现数据拷贝 - 工作过程 - 通过DMA拷贝技术将磁盘数据拷贝到内核缓冲区 - 缓冲区描述符和数据长度传到socket缓冲区,这样网卡的SG-DMA控制器就可以直接将内核缓冲区的数据拷贝到网卡缓冲区,而不再需要借助socket缓冲区,多一次拷贝  优化后:两次上下文切换+两次拷贝 ## Netty 中如何实现零拷贝 netty中的零拷贝和操作系统中零拷贝有点不一样,操作系统实现零拷贝主要是在内核态的优化,netty完全是在用户状态实现零拷贝。 ### CompositeByteBuf 实现零拷贝 通过将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝 - 常规使用 ``` ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes()); allBuf.writeBytes(header); allBuf.writeBytes(body); ``` - 使用CompositeByteBuf ``` CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); compositeByteBuf.addComponents(true, header, body); ``` ### wrap 实现零拷贝 将byte[]数组、ByteBuf、ByteBuffer等包装成一个Netty ByteBuf对象进而避免了拷贝操作 - 常规使用 ``` byte[] bytes = ... ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeBytes(bytes); ``` - 使用wrap ``` byte[] bytes = ... ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes); ``` ### slice 实现零拷贝 将ByteBuf 拆解成多个ByteBuf,但是共享同一存储空间不同分区,避免了内存拷贝 ``` ByteBuf byteBuf = ... ByteBuf header = byteBuf.slice(0, 5); ByteBuf body = byteBuf.slice(5, 10); ``` ### FileRegion实现零拷贝 通过FileRegion包装的FileChannel.tranferTo实现文件传输,可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。 - 传统文件传输 ``` public static void copyFile(String srcFile, String destFile) throws Exception { byte[] temp = new byte[1024]; FileInputStream in = new FileInputStream(srcFile); FileOutputStream out = new FileOutputStream(destFile); int length; while ((length = in.read(temp)) != -1) { out.write(temp, 0, length); } in.close(); out.close(); } ``` - file Region ``` public static void copyFileWithFileChannel(String srcFileName, String destFileName) throws Exception { RandomAccessFile srcFile = new RandomAccessFile(srcFileName, "r"); FileChannel srcFileChannel = srcFile.getChannel(); RandomAccessFile destFile = new RandomAccessFile(destFileName, "rw"); FileChannel destFileChannel = destFile.getChannel(); long position = 0; long count = srcFileChannel.size(); srcFileChannel.transferTo(position, count, destFileChannel); } ```发布于 2025-06-13 23:37 •IP属地 河南举报评论收藏
你会等一个人三五年吗?劳拉### 什么是 Netty 的零拷贝? 零拷贝在操作系统中是指避免数据在用户态和内核态之间多次拷贝,从而提高传输效率。在 Netty 中,零拷贝的概念稍有不同,主要指在操作数据时,减少内存区域之间的数据拷贝操作,优化性能。 ### Netty 零拷贝的实现方式 #### 1. 直接使用堆外内存 Netty 使用 `DirectByteBuf` 分配堆外内存,数据直接写入堆外内存后,可以直接传递到内核态,避免 JVM 堆到堆外内存的拷贝。减少了一次拷贝操作,提升了 I/O 性能。 #### 2. CompositeByteBuf `CompositeByteBuf` 用于将多个 `ByteBuf` 组合成一个逻辑上的整体,而无需将它们的数据复制到新的缓冲区中。底层通过引用多个缓冲区的指针来实现逻辑合并,减少了内存拷贝。 #### 3. Unpooled.wrappedBuffer `Unpooled.wrappedBuffer` 可以将多个数据源(如 `byte[]`、`ByteBuf`、`ByteBuffer`)包装成一个 `ByteBuf` 对象,而不会发生数据拷贝。生成的 `ByteBuf` 与原始数据共享底层存储。 #### 4. ByteBuf.slice `ByteBuf.slice` 将一个 `ByteBuf` 切分为多个共享底层存储的 `ByteBuf`,这些切片是原始缓冲区的视图,可以避免数据复制操作,适合场景包括解析网络协议的头部和主体。 #### 5. FileRegion 文件传输 Netty 使用 `FileRegion` 实现文件传输,底层通过 JDK 的 `FileChannel#transferTo` 方法。这种方式利用操作系统的 `sendfile` 系统调用,将文件缓冲区的数据直接传输到目标 `Channel`,避免用户态与内核态的拷贝。 ### 操作系统级零拷贝的实现 `sendfile` 是操作系统提供的一种零拷贝机制,主要步骤包括: 1. 文件数据从磁盘加载到内核态页缓存。 2. 数据从页缓存直接传递到网络 `Socket` 缓存。 3. 数据通过网络发送,无需进入用户态。 Netty 利用 `FileRegion` 封装这一机制,显著提升大文件传输效率。 ### Netty 零拷贝的优势 1. **减少内存拷贝次数**:避免数据在 JVM 和操作系统之间或不同内存区域间的多次拷贝。 2. **降低内存占用**:通过共享底层存储机制(如 `CompositeByteBuf` 和 `ByteBuf.slice`),减少内存分配和复制。 3. **提升传输效率**:利用操作系统的零拷贝机制(如 `sendfile`),直接在内核空间完成数据传输,降低延迟和 CPU 使用率。 4. **增强扩展性**:高效的内存管理机制支持更高并发和数据吞吐。 Netty 的零拷贝机制在高性能网络通信场景中表现尤为出色,使其成为分布式系统和微服务开发的重要组件。发布于 2024-12-04 06:01 •IP属地 浙江举报评论收藏
公众号进入网站体验更佳

