【Netty基础】Netty的“零拷贝”

来源:互联网 发布:知乎live 编辑:程序博客网 时间:2024/06/13 04:14

零拷贝的定义

  • Zero-copy, 就是在操作数据时, 不需要将数据 buffer 从一个内存区域拷贝到另一个内存区域. 因为少了一次内存的拷贝, 因此 CPU 的效率就得到的提升.

  • 在 OS 层面上的 Zero-copy 通常指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据。

  • 但Netty 中的 Zero-copy 与 OS 的 Zero-copy 不太一样, Netty的 Zero-coyp 完全是在用户态(Java 层面)的, 它的 Zero-copy 的更多的是偏向于 优化数据操作 。

Netty的“零拷贝”主要体现以下几个方面:

1.Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

2.Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。

3.通过 FileRegion 包装的FileChannel.tranferTo方法 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环write方式导致的内存拷贝问题。

4.通过 wrap 操作, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而避免了拷贝操作。

零拷贝的具体分析

1.ByteBuffer分配Direct Buffers

源码如下:

这里写图片描述
这里写图片描述

分析:从源码知,ByteBuffer由ChannelConfig分配,而ChannelConfig创建ByteBufAllocator默认使用Direct Buffer,这就避免了读写数据的二次内存拷贝问题,从而实现了读写Socket的零拷贝功能,

2.用CompositeByteBuf 类实现了将多个 ByteBuf 合并为一个逻辑上的 ByteBuf

例:

//定义两个ByteBuf类型的 body 和 header CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();compositeByteBuf.addComponents(true, header, body);

分析:addComponents方法将 header 与 body 合并为一个逻辑上的 ByteBuf, 这两个 ByteBuf 在CompositeByteBuf 内部都是单独存在的, CompositeByteBuf 只是逻辑上是一个整体

图解:
这里写图片描述

注:

  • addComponents方法的参数是 true, 它表示当添加新的 ByteBuf 时, 自动递增 CompositeByteBuf 的 writeIndex,若没有这个参数,那么 compositeByteBuf 的 writeIndex 仍然是0, 就不可能从 compositeByteBuf 中读取到数据,

  • 除了直接使用 CompositeByteBuf 类外, 还可以使用 Unpooled.wrappedBuffer 方法, 它底层封装了 CompositeByteBuf 操作,
    例:ByteBuf allByteBuf = Unpooled.wrappedBuffer(header, body);

3.通过 FileRegion 实现零拷贝

例3.1:使用NIO实现零拷贝

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);}

分析:有了 FileChannel 后, 就可以直接将源文件的内容通过transferTo)方法直接拷贝到目的文件中, 而不需要额外借助一个临时 buffer, 避免了不必要的内存操作.

例3.2 :Netty官网的例子:

public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {    RandomAccessFile raf = null;    long length = -1;    try {        // 1. 通过 RandomAccessFile 打开一个文件.        raf = new RandomAccessFile(msg, "r");        length = raf.length();    } catch (Exception e) {        ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');        return;    } finally {        if (length < 0 && raf != null) {            raf.close();        }    }    ctx.write("OK: " + raf.length() + '\n');    if (ctx.pipeline().get(SslHandler.class) == null) {        // SSL not enabled - can use zero-copy file transfer.        // 2. 调用 raf.getChannel() 获取一个 FileChannel.        // 3. 将 FileChannel 封装成一个 DefaultFileRegion        ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));    } else {        // SSL enabled - cannot use zero-copy file transfer.        ctx.write(new ChunkedFile(raf));    }    ctx.writeAndFlush("\n");}

分析:通过 RandomAccessFile 打开一个文件, 然后 Netty 使用了 DefaultFileRegion 来封装一个 FileChannel,然后就可以直接通过它将文件的内容直接写入 Channel 中, 而不需要传统方式:拷贝文件内容到临时 buffer, 然后再将 buffer 写入 Channel.

4.通过 wrap / slice 实现零拷贝

例4.1: wrap方法

ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);

分析:通过wrappedBuffer 方法来将 bytes 包装成为一个 UnpooledHeapByteBuf 对象, 而在包装的过程中, 是不会有拷贝操作的.

例4.2:slice 方法

ByteBuf header = byteBuf.slice(0, 5);ByteBuf body = byteBuf.slice(5, 10);

分析: slice 操作可以将一个 ByteBuf 切片 为多个共享一个存储区域的 ByteBuf 对象.它产生 header 和 body 的过程是没有拷贝操作的, header 和 body 对象在内部其实是共享了 byteBuf 存储空间的不同部分而已.

注:也可以设置Netty的接收Buffer为堆内存模式,有两种方法

  • boot.option(ChannelOption.ALLOCATOR,PooledByteBufAllocator.DEFAULT)

  • socketchannel.config.setAllocator(UnpooledByteBufAllocator.DEFAULT)



本人才疏学浅,若有错,请指出,谢谢!
如果你有更好的建议,可以留言我们一起讨论,共同进步!
衷心的感谢您能耐心的读完本篇博文。


参考资料:

  1. 对 Netty 的Zero Copy理解

  2. Netty高性能之道