美文网首页netty程序员Java学习笔记
Netty4(八): 零拷贝 与 CompositeByteBu

Netty4(八): 零拷贝 与 CompositeByteBu

作者: 聪明的奇瑞 | 来源:发表于2018-03-19 17:42 被阅读84次

什么是零拷贝?

  • 所谓的零拷贝(Zero-copy)就是在操作数据时,不需要将数据 buffer 从一个内存区域拷贝到另一个内存区域,因此少了一次内存的拷贝,CPU 的效率就得到了提升
  • 从 OS 层面上的零拷贝通常指避免 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据。例如 Linux 提供的 mmap 系统调用,它可以将一段用户空间内存映射到内核空间,当映射成功后,用户对这块内存区域的修改可以直接反应到内核空间,同样,内核空间对这块区域的修改也直接反映到用户空间。正因为有这样的映射关系,我们就不需要再 用户态(User-space) 与 内核态(Kernel-space) 之间拷贝数据,提高数传输的效率
  • 不过,Netty 中的零拷贝与上面所提到的 OS 层面的零拷贝不太一样,Netty 的零拷贝完全是再用户态(Java 层面)的,它的零拷贝更偏向于优化数据操作的概念
  • Netty 中的零拷贝主要体现在如下几方面:
    • Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免各个 ByteBuf 之间的拷贝
    • 通过 wrap 操作,可以将 byte[] 数组、ByteBuf、ByteBuffer 等包装成一个 Netty ByteBuf 对象,进而避免了拷贝操作
    • ByteBuf 支持 slice 操作,因此可以将 ByteBuf 分解为多个共享同一存储区域的 ByteBuf,避免内存拷贝
    • 通过 FileRegion 包装的 FileChannel.tranferTo 实现文件传输,可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统循环 write 方式导致的内存拷贝问题

通过 CompositeByteBuf 实现零拷贝

  • 假设有一份协议的数据,它由头部和消息体组成,而头部和消息体分别存放在两个 ByteBuf 中,即:
ByteBuf header = ...
ByteBuf body = ...
  • 我们在处理代码中,通常希望将 header 和 body 合并为一个 ByteBuf 方便处理,那么通常的做法是:
ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
allBuf.writeBytes(header);
allBuf.writeBytes(body);
  • 可以看到,我们将 header 和 body 都拷贝到了新的 allBuf 中,这无形的增加了两次额外的数据拷贝操作。下面看一下 CompositeByteBuf 是如何实现这样的需求的:
ByteBuf header = ...
ByteBuf body = ...
 
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body);
  • 上面代码中,定义了一个 CompositeByteBuf 对象然后调用 addComponents 方法将 header 与 body 合并为一个逻辑上的 ByteBuf
public CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers);
CompositeByteBuf
  • 不过值得注意的是,虽然 CompositeByteBuf 看起来是由两个 ByteBuf 组合而成的,但再其内部,这两个 ByteBuf 是单独存在的,CompositeByteBuf 只是逻辑上是一个整体
  • addComponents(boolean increaseWriterIndex, ByteBuf... buffers) 来添加两个 ByteBuf
    • 其中第一个参数是 true, 表示当添加新的 ByteBuf 时, 自动递增 CompositeByteBuf 的 writeIndex
    • 如果我们调用的是
    compositeByteBuf.addComponents(header, body);
    
    • 那么 compositeByteBuf 的 writeIndex 仍然是0, 因此此时我们就不可能从 compositeByteBuf 中读取到数据, 这一点要特别注意

通过 wrap 操作实现零拷贝

  • 假设我们有一个 byte 数组,希望把它转换为 ByteBuf 对象以便后续操作,那么传统的做法时将 byte 数组写入到 ByteBuf 中,即:
byte[] bytes = ...
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(bytes);
  • 显然这种方式也是有一个额外的拷贝操作,我们可以使用 Unpooled 的相关方法,包装这个 byte 数组来生成一个新的 ByteBuf 实例,而不需要进行拷贝操作
byte[] bytes = ...
ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);

通过 slice 操作实现零拷贝

  • slice 操作和 wrap 操作正好相反,Unpooled.wrappedBuffer 可以将多个 ByteBuf 合并成一个,而 slice 操作可以将一个 ByteBuf 切分为多个共享一个内存区域的 ByteBuf 对象,ByteBuf 提供了两个 slice 操作方法
public ByteBuf slice();
public ByteBuf slice(int index, int length);
  • 不带参数的 slice() 方法等同于 buf.slice(buf.readerIndex(), buf.readableBytes()),即返回 buf 中可读部分的切片,而 slice(int index, int length) 方法可以设置不同的参数来获取到 buf 的不同区域的切片
ByteBuf byteBuf = ...
ByteBuf header = byteBuf.slice(0, 5);
ByteBuf body = byteBuf.slice(5, 10);
  • slice 方法产生的 header 和 body 的过程都是没有拷贝操作的,header 和 body 对象再内部其实是共享了 ByteBuf 的存储空间的不同部分而已
slice

通过 FileRegion 实现零拷贝

  • 在 Netty 中使用 FileRegion 实现文件传输的零拷贝,不过在底层是依赖于 Java NIO 的 FileChannel.transfer 的零拷贝功能
  • 弱使用最基础的 JAVA IO 实现一个文件的拷贝功能,那么使用传统的方式代码如下
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();
}
  • 上面是一个典型的读写二进制文件的代码实现,上面的代码中不断从源文件中读取指定长度的数据到 temp 数组中,然后再将 temp 中的内容写入目的文件,这样的拷贝操作对于大文件时,频繁的内存拷贝操作会消耗大量系统资源
  • 下面看一下 JavaNIO 中的 FileChannel 如何实现零拷贝
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 后可以直接将源文件的内容拷贝到目的地文件中,不需要再借助一个临时的 buffer,避免了不必要的内存操作
  • 最后看下 Netty 中是怎么使用 FileRegion 来实现零拷贝传输一个文件的
@Override
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,即:
new DefaultFileRegion(raf.getChannel(), 0, length)
  • 当有了 FileRegion 后就可以通过它将文件的内容直接写入 Channel 中,而不需要像传统的做法,拷贝文件内容到临时 buffer,再将 buffer 写入 Channel,零拷贝操作对传输大文件有着很大的帮助

相关文章

  • Netty4(八): 零拷贝 与 CompositeByteBu

    什么是零拷贝? 所谓的零拷贝(Zero-copy)就是在操作数据时,不需要将数据 buffer 从一个内存区域拷贝...

  • 常见的两种零拷贝技术

    什么是零拷贝? 在讲解零拷贝之前,我们先来了解一下为什么要引入零拷贝,以及零拷贝解决了什么问题。 内核态与用户态 ...

  • NIO与零拷贝

    一、是什么 先来看如下一段代码: 这段代码就是读取一个文件,然后再把它写出去,看起来就几行代码,其实涉及到多次拷贝...

  • NIO与零拷贝

    1 零拷贝原理 1.1 传统IO4次拷贝3次切换 2.1优化后3次拷贝2次切换 零拷贝是从操作系统角度看的,是没有...

  • rocketMQ零拷贝、kafka零拷贝、netty零拷贝分析

    1.RocketMQ零拷贝?Netty零拷贝?kafka零拷贝?到底是个什么玩意儿!什么page cache?什么...

  • linux的零拷贝

    零拷贝主要是为了解决用户态与内核态之间数据的拷贝,减少cpu的负担。下面的图描述了常见的数据处理过程。 零拷贝的实...

  • Netty之二NIO与零拷贝

    个人专题目录 1. Nio与零拷贝 零拷贝是服务器网络编程的关键,任何性能优化都离不开。在 Java 程序员的世界...

  • 你知道Netty的零拷贝机制原理吗?这次带你彻底搞懂!

    前言 理解零拷贝,零拷贝是Netty的重要特性之一,而究竟什么是零拷贝呢?WIKI中对其有如下定义: “Zero-...

  • Netty面试常驻题:你知道Netty的零拷贝机制吗?

    理解零拷贝 零拷贝是Netty的重要特性之一,而究竟什么是零拷贝呢?WIKI中对其有如下定义: "Zero-cop...

  • Netty零拷贝

    Netty中的零拷贝与我们传统理解的零拷贝不太一样。传统的零拷贝指的是数据传输过程中,不需要CPU进行数据的拷贝。...

网友评论

    本文标题:Netty4(八): 零拷贝 与 CompositeByteBu

    本文链接:https://www.haomeiwen.com/subject/ipbpqftx.html