Netty 的零拷贝是怎么实现的?

#中间件#面试八股

Netty 的零拷贝是怎么实现的?

结论先行:Netty 的“零拷贝”是两类东西的合集。操作系统层面的零拷贝指 sendfile、mmap、writev,减少用户态与内核态之间的数据拷贝;Netty 层面的零拷贝指 CompositeByteBuf、slice()、duplicate()、wrap() 这些“共享底层内存、不复制字节”的缓冲区操作。前者省 CPU 与内存带宽,后者省 JVM 堆内存与 GC。

一、两类零拷贝

类型手段省掉什么
OS 零拷贝FileRegion(sendfile)内核缓冲区 → 用户缓冲区的拷贝
OS 零拷贝mmap 映射文件同上,但可随机读
OS 零拷贝writev 散写多次小写合并为一次系统调用
Netty 零拷贝CompositeByteBuf合并多个 buffer 时的内存复制
Netty 零拷贝slice() / duplicate()新建视图而不复制内容
Netty 零拷贝Unpooled.wrappedBuffer()包装已有数组/ByteBuffer
Netty 零拷贝堆外 DirectByteBufJVM 堆 → 直接内存的一次拷贝

二、传统读写与 sendfile 的拷贝次数

传统 read + write(4 次拷贝 + 4 次上下文切换)
磁盘 → 内核页缓存 → 用户缓冲区 → Socket 缓冲区 → 网卡

sendfile(2 次拷贝,DMA 参与)
磁盘 → 内核页缓存 ─────────────→ Socket 缓冲区 → 网卡
// 用 FileRegion 传输文件,走 sendfile
FileInputStream in = new FileInputStream("data.bin");
FileRegion region = new DefaultFileRegion(in.getChannel(), 0, in.getChannel().size());
ctx.writeAndFlush(region).addListener(ChannelFutureListener.CLOSE);

注意:FileRegion 只在支持 sendfile 的传输实现上生效(如 NioSocketChannel 在 Linux 上),且不支持 SSL/TLS,因为加密必须读进用户态。

三、CompositeByteBuf 的逻辑合并

两条消息分别编码到 buf1、buf2
传统做法:新建 buf3,把 buf1、buf2 的内容 copy 进去(2 次内存复制)

CompositeByteBuf:只记录 [buf1, buf2] 两个引用
  ┌───────────────┐
  │  header(buf1) │  readerIndex 跨组件移动
  ├───────────────┤
  │  body(buf2)   │
  └───────────────┘
  零复制,读取时按组件边界自动切换
CompositeByteBuf composite = Unpooled.compositeBuffer();
composite.addComponents(true, headerBuf, bodyBuf);
ctx.writeAndFlush(composite);

四、slice 与 duplicate

ByteBuf buf = ctx.alloc().buffer(16);
buf.writeBytes("HEADER-PAYLOAD".getBytes());

ByteBuf header = buf.slice(0, 6);      // 共享内存,不复制
ByteBuf body   = buf.slice(6, 7);      // 同上
// 修改 header 会反映到 buf,因为是同一块内存

retainedSlice() 会额外把引用计数加一,避免父缓冲区释放后子视图变成野指针——这是最容易出错的地方。

常见追问

  • 追问:Netty 的零拷贝和 Kafka 的零拷贝是一回事吗?不完全。Kafka 用的是 OS 层 sendfile(FileChannel.transferTo),Netty 同时提供 OS 层与缓冲区层两类。
  • 追问:零拷贝一定更快吗?小数据量下 CompositeByteBuf 的组件遍历有额外开销,不一定比复制快;数据量大、复制成本高时收益明显。
  • 追问:用了零拷贝还要 release 吗?要。引用计数与是否复制无关,retainedSlice 拿到的视图必须自己释放。

小结

  • OS 零拷贝省内核与用户态之间的拷贝,Netty 零拷贝省堆内复制。
  • FileRegion 走 sendfile,但不支持 TLS。
  • CompositeByteBuf 与 slice 通过共享内存实现逻辑拼接。
  • 视图与引用计数绑定,漏 release 会内存泄漏。
笔记加载中…