Netty:零拷贝

零拷贝(Zero-Copy)详解

什么是零拷贝?

传统 I/O 操作中,数据需要在内核空间和用户空间之间多次复制,零拷贝通过减少或消除这些复制操作来提升性能。


传统 I/O 的数据拷贝过程

以文件发送到网络为例,传统方式需要 4 次拷贝 + 4 次上下文切换

复制代码
1. 磁盘 → 内核缓冲区    (DMA 拷贝,第 1 次)
2. 内核缓冲区 → 用户内存(CPU 拷贝,第 2 次)
3. 用户内存 → Socket 缓冲区(CPU 拷贝,第 3 次)
4. Socket 缓冲区 → 网卡(DMA 拷贝,第 4 次)

磁盘文件 → [内核] → [用户] → [内核 Socket] → 网卡
           ↑ 上下文切换    ↑ 上下文切换

问题:

  • 2 次 CPU 拷贝不必要,浪费 CPU 资源
  • 4 次上下文切换,增加系统开销

零拷贝的优化

通过 sendfile() 系统调用,减少到 2 次拷贝 + 2 次上下文切换

复制代码
1. 磁盘 → 内核缓冲区    (DMA 拷贝)
2. 内核缓冲区 → 网卡    (DMA 拷贝,直接传递描述符)

磁盘文件 → [内核] → 网卡
           ↑ 直接传递,无需经过用户空间

效果:

  • 减少 2 次 CPU 拷贝
  • 减少 2 次上下文切换
  • 性能提升明显(尤其是大文件传输)

Netty 的零拷贝实现

Netty 在多个层面实现了零拷贝:

实现方式 说明 使用场景
CompositeByteBuf 合并多个 ByteBuf,无需数据复制 协议拼接(如 HTTP 头 + 体)
FileRegion.transferTo() 基于 sendfile(),文件直接传输 文件传输
ByteBuf.slice() 创建视图,共享底层内存 数据分片处理
Unpooled.wrappedBuffer() 包装字节数组,无需复制 数组转 ByteBuf

1. CompositeByteBuf:逻辑合并,物理分离

java 复制代码
// 传统方式:需要复制数据
ByteBuf header = ...;
ByteBuf body = ...;
ByteBuf fullBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
fullBuf.writeBytes(header);
fullBuf.writeBytes(body);  // 复制了 2 次

// 零拷贝方式:只合并引用
CompositeByteBuf composite = ByteBufAllocator.DEFAULT.compositeBuffer();
composite.addComponents(true, header, body);  // 无数据复制

原理:

复制代码
传统方式:                零拷贝方式:
[Header] → 复制 → [Full]   [Header] ─┐
[Body]   → 复制 → [Full]   [Body]   ─┼→ [CompositeByteBuf]
                             只维护引用,不复制数据

2. FileRegion:文件直接传输

java 复制代码
// 传统方式:文件 → 用户内存 → 网卡
RandomAccessFile file = new RandomAccessFile("data.txt", "r");
byte[] buffer = new byte[(int) file.length()];
file.read(buffer);  // 拷贝到用户内存
channel.write(buffer);  // 再拷贝到网卡

// 零拷贝方式:文件 → 网卡(通过 sendfile)
FileRegion region = new DefaultFileRegion(file.getChannel(), 0, file.length());
channel.writeAndFlush(region);  // 直接传输,无需经过用户内存

3. slice():共享内存切片

java 复制代码
ByteBuf buf = Unpooled.buffer(10);
buf.writeBytes("HelloWorld".getBytes());

ByteBuf header = buf.slice(0, 5);   // "Hello"
ByteBuf body = buf.slice(5, 5);     // "World"

// header 和 body 共享 buf 的底层内存,无复制

原理:

复制代码
原始 ByteBuf:
[H e l l o W o r l d]
 0 1 2 3 4 5 6 7 8 9

slice(0,5) → 指向 [0:4]
slice(5,5) → 指向 [5:9]

两者共享同一块内存,只是索引范围不同

零拷贝性能对比(估算)

场景 传统拷贝 零拷贝 提升
1MB 文件传输 4 次拷贝 2 次拷贝 ~50%
100MB 文件传输 4 次拷贝 2 次拷贝 ~60%
协议拼接(多次 write) N 次复制 0 次复制 显著

⚠️ 注意:零拷贝主要优化 I/O 密集型场景,CPU 密集型场景收益有限。