Netty 系列 零拷贝

Posted by lichao modified on December 12, 2019

Netty 中的 Zero-copy 与 OS 层面上的 Zero-copy 不太一样(见/io模块/零拷贝), Netty的 Zero-coyp 完全是在用户态(Java 层面)的, 它的 Zero-copy 的更多的是偏向于 优化数据操作 这样的概念。

零拷贝

  1. Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝.
  2. 通过 wrap 操作, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而避免了拷贝操作.
  3. ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝.
  4. 通过 FileRegion 包装的FileChannel.tranferTo 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel, 避免了传统通过循环 write 方式导致的内存拷贝问题.

CompositeByteBuf

通过 CompositeByteBuf 实现零拷贝

使用实例

假设有一份协议数据, 由消息头和消息体组成, 而头部和消息体是分别存放在两个 ByteBuf 中,即: 存储概览 方法将 header 与 body 合并为一个逻辑上的 ByteBuf, 即:

1
2
3
4
5
ByteBuf header = ...
ByteBuf body = ...

CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body); // 其中第一个参数是 true, 表示当添加新的 ByteBuf 时, 自动递增 CompositeByteBuf 的 writeIndex

定义了一个 CompositeByteBuf 对象, 然后调用:

1
2
3
public CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers) {
...
}

虽然看起来 CompositeByteBuf 是由两个 ByteBuf 组合而成的, 不过在 CompositeByteBuf 内部, 这两个 ByteBuf 都是单独存在的, CompositeByteBuf 只是逻辑上是一个整体

除了上面直接使用 CompositeByteBuf 类外,还可以使用 Unpooled.wrappedBuffer 方法, 它底层封装了 CompositeByteBuf 操作, 因此使用起来更加方便:

1
2
3
4
ByteBuf header = ...
ByteBuf body = ...

ByteBuf allByteBuf = Unpooled.wrappedBuffer(header, body);

wrap

通过 wrap 操作实现零拷贝

使用实例

使用 Unpooled 的相关方法, 包装这个 byte 数组, 生成一个新的 ByteBuf 实例, 而不需要进行拷贝操作。 代码为:

1
2
byte[] bytes = ...
ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);

通过 Unpooled.wrappedBuffer 方法将 bytes 包装成一个 UnpooledHeapByteBuf 对象,而在包装的过程中, 是不会有拷贝操作的。 即最后生成的 ByteBuf 对象是和 bytes 数组共用了同一个存储空间, 对 bytes 的修改也会反映到 ByteBuf 对象中。

slice

通过 slice 操作实现零拷贝。slice 操作和 wrap 操作刚好相反, Unpooled.wrappedBuffer 可以将多个 ByteBuf 合并为一个, 而 slice 操作可以将一个 ByteBuf 切片 为多个共享一个存储区域的 ByteBuf 对象。

存储概览 用 slice 方法产生 header 和 body 的过程是没有拷贝操作的, header 和 body 对象在内部其实是共享了 byteBuf 存储空间的不同部分而已。

ByteBuf 提供了两个 slice 操作方法:

1
2
public ByteBuf slice();
public ByteBuf slice(int index, int length);

FileRegion

Netty 中使用 FileRegion 实现文件传输的零拷贝, 不过在底层 FileRegion 是依赖于 Java NIO FileChannel.transfer 的零拷贝功能。

摘自:博客园